mirror of
https://github.com/php/php-src.git
synced 2025-08-15 13:38:49 +02:00
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 GH-7711.
This commit is contained in:
parent
78ef25b21c
commit
38460c2c94
12 changed files with 1057 additions and 82 deletions
1
NEWS
1
NEWS
|
@ -17,6 +17,7 @@ PHP NEWS
|
|||
- Standard:
|
||||
. net_get_interfaces() also reports wireless network interfaces on Windows.
|
||||
(Yurun)
|
||||
. Finished AVIF support in getimagesize(). (Yannis Guyon)
|
||||
|
||||
- Zip:
|
||||
. add ZipArchive::clearError() method
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
15. ext/phar/zip.c portion extracted from libzip
|
||||
16. libbcmath (ext/bcmath) see ext/bcmath/libbcmath/LICENSE
|
||||
17. ext/mbstring/ucgendat portions based on the ucgendat.c from the OpenLDAP
|
||||
18. avifinfo (ext/standard/libavifinfo) see ext/standard/libavifinfo/LICENSE
|
||||
|
||||
|
||||
3. pcre2lib (ext/pcre)
|
||||
|
@ -591,7 +592,7 @@ OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
|||
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
16. ext/mbstring/ucgendat portions based on the ucgendat.c from the OpenLDAP
|
||||
17. ext/mbstring/ucgendat portions based on the ucgendat.c from the OpenLDAP
|
||||
|
||||
The OpenLDAP Public License
|
||||
Version 2.8, 17 August 2003
|
||||
|
|
|
@ -129,6 +129,11 @@ PHP 8.2 UPGRADE NOTES
|
|||
- OCI8:
|
||||
. The minimum Oracle Client library version required is now 11.2.
|
||||
|
||||
- Standard:
|
||||
. getimagesize() now reports the actual image dimensions, bits and channels
|
||||
of AVIF images. Previously, the dimensions have been reported as 0x0, and
|
||||
bits and channels have not been reported at all.
|
||||
|
||||
- Zip:
|
||||
. extension updated to 1.20.0 with new methods:
|
||||
ZipArchive::clearError, getStreamName and getStreamIndex
|
||||
|
|
|
@ -460,7 +460,7 @@ PHP_NEW_EXTENSION(standard, array.c base64.c basic_functions.c browscap.c crc32.
|
|||
http_fopen_wrapper.c php_fopen_wrapper.c credits.c css.c \
|
||||
var_unserializer.c ftok.c sha1.c user_filters.c uuencode.c \
|
||||
filters.c proc_open.c streamsfuncs.c http.c password.c \
|
||||
random.c net.c hrtime.c crc32_x86.c,,,
|
||||
random.c net.c hrtime.c crc32_x86.c libavifinfo/avifinfo.c,,,
|
||||
-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
|
||||
|
||||
PHP_ADD_MAKEFILE_FRAGMENT
|
||||
|
|
|
@ -37,6 +37,7 @@ EXTENSION("standard", "array.c base64.c basic_functions.c browscap.c \
|
|||
user_filters.c uuencode.c filters.c proc_open.c password.c \
|
||||
streamsfuncs.c http.c flock_compat.c random.c hrtime.c", false /* never shared */,
|
||||
'/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1');
|
||||
ADD_SOURCES("ext/standard/libavifinfo", "avifinfo.c", "standard");
|
||||
PHP_STANDARD = "yes";
|
||||
ADD_MAKEFILE_FRAGMENT();
|
||||
PHP_INSTALL_HEADERS("", "ext/standard");
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#endif
|
||||
#include "fopen_wrappers.h"
|
||||
#include "ext/standard/fsock.h"
|
||||
#include "libavifinfo/avifinfo.h"
|
||||
#if HAVE_UNISTD_H
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
@ -1155,95 +1156,76 @@ static struct gfxinfo *php_handle_webp(php_stream * stream)
|
|||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ php_handle_avif
|
||||
* There's no simple way to get this information - so, for now, this is unsupported.
|
||||
* Simply return 0 for everything.
|
||||
*/
|
||||
static struct gfxinfo *php_handle_avif(php_stream * stream) {
|
||||
return ecalloc(1, sizeof(struct gfxinfo));
|
||||
/* {{{ User struct and stream read/skip implementations for libavifinfo API */
|
||||
struct php_avif_stream {
|
||||
php_stream* stream;
|
||||
uint8_t buffer[AVIFINFO_MAX_NUM_READ_BYTES];
|
||||
};
|
||||
|
||||
static const uint8_t* php_avif_stream_read(void* stream, size_t num_bytes) {
|
||||
struct php_avif_stream* avif_stream = (struct php_avif_stream*)stream;
|
||||
|
||||
if (avif_stream == NULL || avif_stream->stream == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
if (php_stream_read(avif_stream->stream, (char*)avif_stream->buffer, num_bytes) != num_bytes) {
|
||||
avif_stream->stream = NULL; /* fail further calls */
|
||||
return NULL;
|
||||
}
|
||||
return avif_stream->buffer;
|
||||
}
|
||||
|
||||
static void php_avif_stream_skip(void* stream, size_t num_bytes) {
|
||||
struct php_avif_stream* avif_stream = (struct php_avif_stream*)stream;
|
||||
|
||||
if (avif_stream == NULL || avif_stream->stream == NULL) {
|
||||
return;
|
||||
}
|
||||
if (php_stream_seek(avif_stream->stream, num_bytes, SEEK_CUR)) {
|
||||
avif_stream->stream = NULL; /* fail further calls */
|
||||
}
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ php_ntohl
|
||||
* Convert a big-endian network uint32 to host order -
|
||||
* which may be either little-endian or big-endian.
|
||||
* Thanks to Rob Pike via Joe Drago:
|
||||
* https://commandcenter.blogspot.nl/2012/04/byte-order-fallacy.html
|
||||
/* {{{ php_handle_avif
|
||||
* Parse AVIF features
|
||||
*
|
||||
* The stream must be positioned at the beginning of a box, so it does not
|
||||
* matter whether the "ftyp" box was already read by php_is_image_avif() or not.
|
||||
* It will read bytes from the stream until features are found or the file is
|
||||
* declared as invalid. Around 450 bytes are usually enough.
|
||||
* Transforms such as mirror and rotation are not applied on width and height.
|
||||
*/
|
||||
static uint32_t php_ntohl(uint32_t val) {
|
||||
uint8_t data[4];
|
||||
static struct gfxinfo *php_handle_avif(php_stream * stream) {
|
||||
struct gfxinfo* result = NULL;
|
||||
AvifInfoFeatures features;
|
||||
struct php_avif_stream avif_stream;
|
||||
avif_stream.stream = stream;
|
||||
|
||||
memcpy(&data, &val, sizeof(data));
|
||||
return ((uint32_t)data[3] << 0) |
|
||||
((uint32_t)data[2] << 8) |
|
||||
((uint32_t)data[1] << 16) |
|
||||
((uint32_t)data[0] << 24);
|
||||
if (AvifInfoGetFeaturesStream(&avif_stream, php_avif_stream_read, php_avif_stream_skip, &features) == kAvifInfoOk) {
|
||||
result = (struct gfxinfo*)ecalloc(1, sizeof(struct gfxinfo));
|
||||
result->width = features.width;
|
||||
result->height = features.height;
|
||||
result->bits = features.bit_depth;
|
||||
result->channels = features.num_channels;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ php_is_image_avif
|
||||
* detect whether an image is of type AVIF
|
||||
*
|
||||
* An AVIF image will start off a header "box".
|
||||
* This starts with with a four-byte integer containing the number of bytes in the filetype box.
|
||||
* This must be followed by the string "ftyp".
|
||||
* Next comes a four-byte string indicating the "major brand".
|
||||
* If that's "avif" or "avis", this is an AVIF image.
|
||||
* Next, there's a four-byte "minor version" field, which we can ignore.
|
||||
* Next comes an array of four-byte strings containing "compatible brands".
|
||||
* These extend to the end of the box.
|
||||
* If any of the compatible brands is "avif" or "avis", then this is an AVIF image.
|
||||
* Otherwise, well, it's not.
|
||||
* For more, see https://mpeg.chiariglione.org/standards/mpeg-4/iso-base-media-file-format/text-isoiec-14496-12-5th-edition
|
||||
* Detect whether an image is of type AVIF
|
||||
*
|
||||
* Only the first "ftyp" box is read.
|
||||
* For a valid file, 12 bytes are usually read, but more might be necessary.
|
||||
*/
|
||||
bool php_is_image_avif(php_stream * stream) {
|
||||
uint32_t header_size_reversed, header_size, i;
|
||||
char box_type[4], brand[4];
|
||||
bool php_is_image_avif(php_stream* stream) {
|
||||
struct php_avif_stream avif_stream;
|
||||
avif_stream.stream = stream;
|
||||
|
||||
ZEND_ASSERT(stream != NULL);
|
||||
|
||||
if (php_stream_read(stream, (char *) &header_size_reversed, 4) != 4) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
header_size = php_ntohl(header_size_reversed);
|
||||
|
||||
/* If the box type isn't "ftyp", it can't be an AVIF image. */
|
||||
if (php_stream_read(stream, box_type, 4) != 4) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (memcmp(box_type, "ftyp", 4)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* If the major brand is "avif" or "avis", it's an AVIF image. */
|
||||
if (php_stream_read(stream, brand, 4) != 4) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!memcmp(brand, "avif", 4) || !memcmp(brand, "avis", 4)) {
|
||||
if (AvifInfoIdentifyStream(&avif_stream, php_avif_stream_read, php_avif_stream_skip) == kAvifInfoOk) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Skip the next four bytes, which are the "minor version". */
|
||||
if (php_stream_read(stream, brand, 4) != 4) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Look for "avif" or "avis" in any member of compatible_brands[], to the end of the header.
|
||||
Note we've already read four groups of four bytes. */
|
||||
|
||||
for (i = 16; i < header_size; i += 4) {
|
||||
if (php_stream_read(stream, brand, 4) != 4) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!memcmp(brand, "avif", 4) || !memcmp(brand, "avis", 4)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
/* }}} */
|
||||
|
|
26
ext/standard/libavifinfo/LICENSE
Normal file
26
ext/standard/libavifinfo/LICENSE
Normal file
|
@ -0,0 +1,26 @@
|
|||
Copyright (c) 2021, Alliance for Open Media. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
107
ext/standard/libavifinfo/PATENTS
Normal file
107
ext/standard/libavifinfo/PATENTS
Normal file
|
@ -0,0 +1,107 @@
|
|||
Alliance for Open Media Patent License 1.0
|
||||
|
||||
1. License Terms.
|
||||
|
||||
1.1. Patent License. Subject to the terms and conditions of this License, each
|
||||
Licensor, on behalf of itself and successors in interest and assigns,
|
||||
grants Licensee a non-sublicensable, perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as expressly stated in this
|
||||
License) patent license to its Necessary Claims to make, use, sell, offer
|
||||
for sale, import or distribute any Implementation.
|
||||
|
||||
1.2. Conditions.
|
||||
|
||||
1.2.1. Availability. As a condition to the grant of rights to Licensee to make,
|
||||
sell, offer for sale, import or distribute an Implementation under
|
||||
Section 1.1, Licensee must make its Necessary Claims available under
|
||||
this License, and must reproduce this License with any Implementation
|
||||
as follows:
|
||||
|
||||
a. For distribution in source code, by including this License in the
|
||||
root directory of the source code with its Implementation.
|
||||
|
||||
b. For distribution in any other form (including binary, object form,
|
||||
and/or hardware description code (e.g., HDL, RTL, Gate Level Netlist,
|
||||
GDSII, etc.)), by including this License in the documentation, legal
|
||||
notices, and/or other written materials provided with the
|
||||
Implementation.
|
||||
|
||||
1.2.2. Additional Conditions. This license is directly from Licensor to
|
||||
Licensee. Licensee acknowledges as a condition of benefiting from it
|
||||
that no rights from Licensor are received from suppliers, distributors,
|
||||
or otherwise in connection with this License.
|
||||
|
||||
1.3. Defensive Termination. If any Licensee, its Affiliates, or its agents
|
||||
initiates patent litigation or files, maintains, or voluntarily
|
||||
participates in a lawsuit against another entity or any person asserting
|
||||
that any Implementation infringes Necessary Claims, any patent licenses
|
||||
granted under this License directly to the Licensee are immediately
|
||||
terminated as of the date of the initiation of action unless 1) that suit
|
||||
was in response to a corresponding suit regarding an Implementation first
|
||||
brought against an initiating entity, or 2) that suit was brought to
|
||||
enforce the terms of this License (including intervention in a third-party
|
||||
action by a Licensee).
|
||||
|
||||
1.4. Disclaimers. The Reference Implementation and Specification are provided
|
||||
"AS IS" and without warranty. The entire risk as to implementing or
|
||||
otherwise using the Reference Implementation or Specification is assumed
|
||||
by the implementer and user. Licensor expressly disclaims any warranties
|
||||
(express, implied, or otherwise), including implied warranties of
|
||||
merchantability, non-infringement, fitness for a particular purpose, or
|
||||
title, related to the material. IN NO EVENT WILL LICENSOR BE LIABLE TO
|
||||
ANY OTHER PARTY FOR LOST PROFITS OR ANY FORM OF INDIRECT, SPECIAL,
|
||||
INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER FROM ANY CAUSES OF
|
||||
ACTION OF ANY KIND WITH RESPECT TO THIS LICENSE, WHETHER BASED ON BREACH
|
||||
OF CONTRACT, TORT (INCLUDING NEGLIGENCE), OR OTHERWISE, AND WHETHER OR
|
||||
NOT THE OTHER PARTRY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
2. Definitions.
|
||||
|
||||
2.1. Affiliate. "Affiliate" means an entity that directly or indirectly
|
||||
Controls, is Controlled by, or is under common Control of that party.
|
||||
|
||||
2.2. Control. "Control" means direct or indirect control of more than 50% of
|
||||
the voting power to elect directors of that corporation, or for any other
|
||||
entity, the power to direct management of such entity.
|
||||
|
||||
2.3. Decoder. "Decoder" means any decoder that conforms fully with all
|
||||
non-optional portions of the Specification.
|
||||
|
||||
2.4. Encoder. "Encoder" means any encoder that produces a bitstream that can
|
||||
be decoded by a Decoder only to the extent it produces such a bitstream.
|
||||
|
||||
2.5. Final Deliverable. "Final Deliverable" means the final version of a
|
||||
deliverable approved by the Alliance for Open Media as a Final
|
||||
Deliverable.
|
||||
|
||||
2.6. Implementation. "Implementation" means any implementation, including the
|
||||
Reference Implementation, that is an Encoder and/or a Decoder. An
|
||||
Implementation also includes components of an Implementation only to the
|
||||
extent they are used as part of an Implementation.
|
||||
|
||||
2.7. License. "License" means this license.
|
||||
|
||||
2.8. Licensee. "Licensee" means any person or entity who exercises patent
|
||||
rights granted under this License.
|
||||
|
||||
2.9. Licensor. "Licensor" means (i) any Licensee that makes, sells, offers
|
||||
for sale, imports or distributes any Implementation, or (ii) a person
|
||||
or entity that has a licensing obligation to the Implementation as a
|
||||
result of its membership and/or participation in the Alliance for Open
|
||||
Media working group that developed the Specification.
|
||||
|
||||
2.10. Necessary Claims. "Necessary Claims" means all claims of patents or
|
||||
patent applications, (a) that currently or at any time in the future,
|
||||
are owned or controlled by the Licensor, and (b) (i) would be an
|
||||
Essential Claim as defined by the W3C Policy as of February 5, 2004
|
||||
(https://www.w3.org/Consortium/Patent-Policy-20040205/#def-essential)
|
||||
as if the Specification was a W3C Recommendation; or (ii) are infringed
|
||||
by the Reference Implementation.
|
||||
|
||||
2.11. Reference Implementation. "Reference Implementation" means an Encoder
|
||||
and/or Decoder released by the Alliance for Open Media as a Final
|
||||
Deliverable.
|
||||
|
||||
2.12. Specification. "Specification" means the specification designated by
|
||||
the Alliance for Open Media as a Final Deliverable for which this
|
||||
License was issued.
|
11
ext/standard/libavifinfo/README.md
Normal file
11
ext/standard/libavifinfo/README.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# AVIF-info
|
||||
|
||||
There is no compact, reliable way to determine the size of an AVIF image. A
|
||||
standalone C snippet called
|
||||
[libavifinfo](https://aomedia.googlesource.com/libavifinfo) was created to
|
||||
partially parse an AVIF payload and to extract the width, height, bit depth and
|
||||
channel count without depending on the full libavif library.
|
||||
|
||||
`avifinfo.h`, `avifinfo.c`, `LICENSE` and `PATENTS` were copied verbatim from: \
|
||||
https://aomedia.googlesource.com/libavifinfo/+/96f34d945ac7dac229feddfa94dbae66e202b838 \
|
||||
They can easily be kept up-to-date the same way.
|
745
ext/standard/libavifinfo/avifinfo.c
Normal file
745
ext/standard/libavifinfo/avifinfo.c
Normal file
|
@ -0,0 +1,745 @@
|
|||
// Copyright (c) 2021, Alliance for Open Media. All rights reserved
|
||||
//
|
||||
// This source code is subject to the terms of the BSD 2 Clause License and
|
||||
// the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
|
||||
// was not distributed with this source code in the LICENSE file, you can
|
||||
// obtain it at www.aomedia.org/license/software. If the Alliance for Open
|
||||
// Media Patent License 1.0 was not distributed with this source code in the
|
||||
// PATENTS file, you can obtain it at www.aomedia.org/license/patent.
|
||||
|
||||
#include "avifinfo.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Status returned when reading the content of a box (or file).
|
||||
typedef enum {
|
||||
kFound, // Input correctly parsed and information retrieved.
|
||||
kNotFound, // Input correctly parsed but information is missing or elsewhere.
|
||||
kTruncated, // Input correctly parsed until missing bytes to continue.
|
||||
kAborted, // Input correctly parsed until stopped to avoid timeout or crash.
|
||||
kInvalid, // Input incorrectly parsed.
|
||||
} AvifInfoInternalStatus;
|
||||
|
||||
static AvifInfoStatus AvifInfoInternalConvertStatus(AvifInfoInternalStatus s) {
|
||||
return (s == kFound) ? kAvifInfoOk
|
||||
: (s == kNotFound || s == kTruncated) ? kAvifInfoNotEnoughData
|
||||
: (s == kAborted) ? kAvifInfoTooComplex
|
||||
: kAvifInfoInvalidFile;
|
||||
}
|
||||
|
||||
// uint32_t is used everywhere in this file. It is unlikely to be insufficient
|
||||
// to parse AVIF headers.
|
||||
#define AVIFINFO_MAX_SIZE UINT32_MAX
|
||||
// AvifInfoInternalFeatures uses uint8_t to store values and the number of
|
||||
// values is clamped to 32 to limit the stack size.
|
||||
#define AVIFINFO_MAX_VALUE UINT8_MAX
|
||||
#define AVIFINFO_UNDEFINED 0
|
||||
// Maximum number of stored associations. Past that, they are skipped.
|
||||
#define AVIFINFO_MAX_TILES 16
|
||||
#define AVIFINFO_MAX_PROPS 32
|
||||
#define AVIFINFO_MAX_FEATURES 8
|
||||
|
||||
// Reads an unsigned integer from 'input' with most significant bits first.
|
||||
// 'input' must be at least 'num_bytes'-long.
|
||||
static uint32_t AvifInfoInternalReadBigEndian(const uint8_t* input,
|
||||
uint32_t num_bytes) {
|
||||
uint32_t value = 0;
|
||||
for (uint32_t i = 0; i < num_bytes; ++i) {
|
||||
value = (value << 8) | input[i];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Convenience macros.
|
||||
|
||||
#if defined(AVIFINFO_LOG_ERROR) // Toggle to log encountered issues.
|
||||
static void AvifInfoInternalLogError(const char* file, int line,
|
||||
AvifInfoInternalStatus status) {
|
||||
const char* kStr[] = {"Found", "NotFound", "Truncated", "Invalid", "Aborted"};
|
||||
fprintf(stderr, " %s:%d: %s\n", file, line, kStr[status]);
|
||||
// Set a breakpoint here to catch the first detected issue.
|
||||
}
|
||||
#define AVIFINFO_RETURN(check_status) \
|
||||
do { \
|
||||
const AvifInfoInternalStatus status_checked = (check_status); \
|
||||
if (status_checked != kFound && status_checked != kNotFound) { \
|
||||
AvifInfoInternalLogError(__FILE__, __LINE__, status_checked); \
|
||||
} \
|
||||
return status_checked; \
|
||||
} while (0)
|
||||
#else
|
||||
#define AVIFINFO_RETURN(check_status) \
|
||||
do { \
|
||||
return (check_status); \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#define AVIFINFO_CHECK(check_condition, check_status) \
|
||||
do { \
|
||||
if (!(check_condition)) AVIFINFO_RETURN(check_status); \
|
||||
} while (0)
|
||||
#define AVIFINFO_CHECK_STATUS_IS(check_status, expected_status) \
|
||||
do { \
|
||||
const AvifInfoInternalStatus status_returned = (check_status); \
|
||||
AVIFINFO_CHECK(status_returned == (expected_status), status_returned); \
|
||||
} while (0)
|
||||
#define AVIFINFO_CHECK_FOUND(check_status) \
|
||||
AVIFINFO_CHECK_STATUS_IS((check_status), kFound)
|
||||
#define AVIFINFO_CHECK_NOT_FOUND(check_status) \
|
||||
AVIFINFO_CHECK_STATUS_IS((check_status), kNotFound)
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Streamed input struct and helper functions.
|
||||
|
||||
typedef struct {
|
||||
void* stream; // User-defined data.
|
||||
read_stream_t read; // Used to fetch more bytes from the 'stream'.
|
||||
skip_stream_t skip; // Used to advance the position in the 'stream'.
|
||||
// Fallback to 'read' if 'skip' is null.
|
||||
} AvifInfoInternalStream;
|
||||
|
||||
// Reads 'num_bytes' from the 'stream'. They are available at '*data'.
|
||||
// 'num_bytes' must be greater than zero.
|
||||
static AvifInfoInternalStatus AvifInfoInternalRead(
|
||||
AvifInfoInternalStream* stream, uint32_t num_bytes, const uint8_t** data) {
|
||||
*data = stream->read(stream->stream, num_bytes);
|
||||
AVIFINFO_CHECK(*data != NULL, kTruncated);
|
||||
return kFound;
|
||||
}
|
||||
|
||||
// Skips 'num_bytes' from the 'stream'. 'num_bytes' can be zero.
|
||||
static AvifInfoInternalStatus AvifInfoInternalSkip(
|
||||
AvifInfoInternalStream* stream, uint32_t num_bytes) {
|
||||
// Avoid a call to the user-defined function for nothing.
|
||||
if (num_bytes > 0) {
|
||||
if (stream->skip == NULL) {
|
||||
const uint8_t* unused;
|
||||
while (num_bytes > AVIFINFO_MAX_NUM_READ_BYTES) {
|
||||
AVIFINFO_CHECK_FOUND(
|
||||
AvifInfoInternalRead(stream, AVIFINFO_MAX_NUM_READ_BYTES, &unused));
|
||||
num_bytes -= AVIFINFO_MAX_NUM_READ_BYTES;
|
||||
}
|
||||
return AvifInfoInternalRead(stream, num_bytes, &unused);
|
||||
}
|
||||
stream->skip(stream->stream, num_bytes);
|
||||
}
|
||||
return kFound;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Features are parsed into temporary property associations.
|
||||
|
||||
typedef struct {
|
||||
uint8_t tile_item_id;
|
||||
uint8_t parent_item_id;
|
||||
} AvifInfoInternalTile; // Tile item id <-> parent item id associations.
|
||||
|
||||
typedef struct {
|
||||
uint8_t property_index;
|
||||
uint8_t item_id;
|
||||
} AvifInfoInternalProp; // Property index <-> item id associations.
|
||||
|
||||
typedef struct {
|
||||
uint8_t property_index;
|
||||
uint32_t width, height;
|
||||
} AvifInfoInternalDimProp; // Property <-> features associations.
|
||||
|
||||
typedef struct {
|
||||
uint8_t property_index;
|
||||
uint8_t bit_depth, num_channels;
|
||||
} AvifInfoInternalChanProp; // Property <-> features associations.
|
||||
|
||||
typedef struct {
|
||||
uint8_t has_primary_item; // True if "pitm" was parsed.
|
||||
uint8_t has_alpha; // True if an alpha "auxC" was parsed.
|
||||
uint8_t primary_item_id;
|
||||
AvifInfoFeatures primary_item_features; // Deduced from the data below.
|
||||
uint8_t data_was_skipped; // True if some loops/indices were skipped.
|
||||
|
||||
uint8_t num_tiles;
|
||||
AvifInfoInternalTile tiles[AVIFINFO_MAX_TILES];
|
||||
uint8_t num_props;
|
||||
AvifInfoInternalProp props[AVIFINFO_MAX_PROPS];
|
||||
uint8_t num_dim_props;
|
||||
AvifInfoInternalDimProp dim_props[AVIFINFO_MAX_FEATURES];
|
||||
uint8_t num_chan_props;
|
||||
AvifInfoInternalChanProp chan_props[AVIFINFO_MAX_FEATURES];
|
||||
} AvifInfoInternalFeatures;
|
||||
|
||||
// Generates the features of a given 'target_item_id' from internal features.
|
||||
static AvifInfoInternalStatus AvifInfoInternalGetItemFeatures(
|
||||
AvifInfoInternalFeatures* f, uint32_t target_item_id, uint32_t tile_depth) {
|
||||
for (uint32_t prop_item = 0; prop_item < f->num_props; ++prop_item) {
|
||||
if (f->props[prop_item].item_id != target_item_id) continue;
|
||||
const uint32_t property_index = f->props[prop_item].property_index;
|
||||
|
||||
// Retrieve the width and height of the primary item if not already done.
|
||||
if (target_item_id == f->primary_item_id &&
|
||||
(f->primary_item_features.width == AVIFINFO_UNDEFINED ||
|
||||
f->primary_item_features.height == AVIFINFO_UNDEFINED)) {
|
||||
for (uint32_t i = 0; i < f->num_dim_props; ++i) {
|
||||
if (f->dim_props[i].property_index != property_index) continue;
|
||||
f->primary_item_features.width = f->dim_props[i].width;
|
||||
f->primary_item_features.height = f->dim_props[i].height;
|
||||
if (f->primary_item_features.bit_depth != AVIFINFO_UNDEFINED &&
|
||||
f->primary_item_features.num_channels != AVIFINFO_UNDEFINED) {
|
||||
return kFound;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Retrieve the bit depth and number of channels of the target item if not
|
||||
// already done.
|
||||
if (f->primary_item_features.bit_depth == AVIFINFO_UNDEFINED ||
|
||||
f->primary_item_features.num_channels == AVIFINFO_UNDEFINED) {
|
||||
for (uint32_t i = 0; i < f->num_chan_props; ++i) {
|
||||
if (f->chan_props[i].property_index != property_index) continue;
|
||||
f->primary_item_features.bit_depth = f->chan_props[i].bit_depth;
|
||||
f->primary_item_features.num_channels = f->chan_props[i].num_channels;
|
||||
if (f->primary_item_features.width != AVIFINFO_UNDEFINED &&
|
||||
f->primary_item_features.height != AVIFINFO_UNDEFINED) {
|
||||
return kFound;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for the bit_depth and num_channels in a tile if not yet found.
|
||||
for (uint32_t tile = 0; tile < f->num_tiles && tile_depth < 3; ++tile) {
|
||||
if (f->tiles[tile].parent_item_id != target_item_id) continue;
|
||||
AVIFINFO_CHECK_NOT_FOUND(AvifInfoInternalGetItemFeatures(
|
||||
f, f->tiles[tile].tile_item_id, tile_depth + 1));
|
||||
}
|
||||
AVIFINFO_RETURN(kNotFound);
|
||||
}
|
||||
|
||||
// Generates the 'f->primary_item_features' from the AvifInfoInternalFeatures.
|
||||
// Returns kNotFound if there is not enough information.
|
||||
static AvifInfoInternalStatus AvifInfoInternalGetPrimaryItemFeatures(
|
||||
AvifInfoInternalFeatures* f) {
|
||||
// Nothing to do without the primary item ID.
|
||||
AVIFINFO_CHECK(f->has_primary_item, kNotFound);
|
||||
// Early exit.
|
||||
AVIFINFO_CHECK(f->num_dim_props > 0 && f->num_chan_props, kNotFound);
|
||||
AVIFINFO_CHECK_FOUND(
|
||||
AvifInfoInternalGetItemFeatures(f, f->primary_item_id, /*tile_depth=*/0));
|
||||
|
||||
// "auxC" is parsed before the "ipma" properties so it is known now, if any.
|
||||
if (f->has_alpha) ++f->primary_item_features.num_channels;
|
||||
return kFound;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Box header parsing and various size checks.
|
||||
|
||||
typedef struct {
|
||||
uint32_t size; // In bytes.
|
||||
uint8_t type[4]; // Four characters.
|
||||
uint32_t version; // 0 or actual version if this is a full box.
|
||||
uint32_t flags; // 0 or actual value if this is a full box.
|
||||
uint32_t content_size; // 'size' minus the header size.
|
||||
} AvifInfoInternalBox;
|
||||
|
||||
// Reads the header of a 'box' starting at the beginning of a 'stream'.
|
||||
// 'num_remaining_bytes' is the remaining size of the container of the 'box'
|
||||
// (either the file size itself or the content size of the parent of the 'box').
|
||||
static AvifInfoInternalStatus AvifInfoInternalParseBox(
|
||||
AvifInfoInternalStream* stream, uint32_t num_remaining_bytes,
|
||||
uint32_t* num_parsed_boxes, AvifInfoInternalBox* box) {
|
||||
const uint8_t* data;
|
||||
// See ISO/IEC 14496-12:2012(E) 4.2
|
||||
uint32_t box_header_size = 8; // box 32b size + 32b type (at least)
|
||||
AVIFINFO_CHECK(box_header_size <= num_remaining_bytes, kInvalid);
|
||||
AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 8, &data));
|
||||
box->size = AvifInfoInternalReadBigEndian(data, sizeof(uint32_t));
|
||||
memcpy(box->type, data + 4, 4);
|
||||
// 'box->size==1' means 64-bit size should be read after the box type.
|
||||
// 'box->size==0' means this box extends to all remaining bytes.
|
||||
if (box->size == 1) {
|
||||
box_header_size += 8;
|
||||
AVIFINFO_CHECK(box_header_size <= num_remaining_bytes, kInvalid);
|
||||
AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 8, &data));
|
||||
// Stop the parsing if any box has a size greater than 4GB.
|
||||
AVIFINFO_CHECK(AvifInfoInternalReadBigEndian(data, sizeof(uint32_t)) == 0,
|
||||
kAborted);
|
||||
// Read the 32 least-significant bits.
|
||||
box->size = AvifInfoInternalReadBigEndian(data + 4, sizeof(uint32_t));
|
||||
} else if (box->size == 0) {
|
||||
box->size = num_remaining_bytes;
|
||||
}
|
||||
AVIFINFO_CHECK(box->size >= box_header_size, kInvalid);
|
||||
AVIFINFO_CHECK(box->size <= num_remaining_bytes, kInvalid);
|
||||
|
||||
const int has_fullbox_header =
|
||||
!memcmp(box->type, "meta", 4) || !memcmp(box->type, "pitm", 4) ||
|
||||
!memcmp(box->type, "ipma", 4) || !memcmp(box->type, "ispe", 4) ||
|
||||
!memcmp(box->type, "pixi", 4) || !memcmp(box->type, "iref", 4) ||
|
||||
!memcmp(box->type, "auxC", 4);
|
||||
if (has_fullbox_header) box_header_size += 4;
|
||||
AVIFINFO_CHECK(box->size >= box_header_size, kInvalid);
|
||||
box->content_size = box->size - box_header_size;
|
||||
// Avoid timeouts. The maximum number of parsed boxes is arbitrary.
|
||||
++*num_parsed_boxes;
|
||||
AVIFINFO_CHECK(*num_parsed_boxes < 4096, kAborted);
|
||||
|
||||
box->version = 0;
|
||||
box->flags = 0;
|
||||
if (has_fullbox_header) {
|
||||
AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &data));
|
||||
box->version = AvifInfoInternalReadBigEndian(data, 1);
|
||||
box->flags = AvifInfoInternalReadBigEndian(data + 1, 3);
|
||||
// See AV1 Image File Format (AVIF) 8.1
|
||||
// at https://aomediacodec.github.io/av1-avif/#avif-boxes (available when
|
||||
// https://github.com/AOMediaCodec/av1-avif/pull/170 is merged).
|
||||
uint32_t is_parsable = 1;
|
||||
if (!memcmp(box->type, "meta", 4)) is_parsable = (box->version <= 0);
|
||||
if (!memcmp(box->type, "pitm", 4)) is_parsable = (box->version <= 1);
|
||||
if (!memcmp(box->type, "ipma", 4)) is_parsable = (box->version <= 1);
|
||||
if (!memcmp(box->type, "ispe", 4)) is_parsable = (box->version <= 0);
|
||||
if (!memcmp(box->type, "pixi", 4)) is_parsable = (box->version <= 0);
|
||||
if (!memcmp(box->type, "iref", 4)) is_parsable = (box->version <= 1);
|
||||
if (!memcmp(box->type, "auxC", 4)) is_parsable = (box->version <= 0);
|
||||
// Instead of considering this file as invalid, skip unparsable boxes.
|
||||
if (!is_parsable) memcpy(box->type, "\0skp", 4); // \0 so not a valid type
|
||||
}
|
||||
return kFound;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Parses a 'stream' of an "ipco" box into 'features'.
|
||||
// "ispe" is used for width and height, "pixi" and "av1C" are used for bit depth
|
||||
// and number of channels, and "auxC" is used for alpha.
|
||||
static AvifInfoInternalStatus ParseIpco(AvifInfoInternalStream* stream,
|
||||
uint32_t num_remaining_bytes,
|
||||
uint32_t* num_parsed_boxes,
|
||||
AvifInfoInternalFeatures* features) {
|
||||
uint32_t box_index = 1; // 1-based index. Used for iterating over properties.
|
||||
do {
|
||||
AvifInfoInternalBox box;
|
||||
AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, num_remaining_bytes,
|
||||
num_parsed_boxes, &box));
|
||||
|
||||
if (!memcmp(box.type, "ispe", 4)) {
|
||||
// See ISO/IEC 23008-12:2017(E) 6.5.3.2
|
||||
const uint8_t* data;
|
||||
AVIFINFO_CHECK(box.content_size >= 8, kInvalid);
|
||||
AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 8, &data));
|
||||
const uint32_t width = AvifInfoInternalReadBigEndian(data + 0, 4);
|
||||
const uint32_t height = AvifInfoInternalReadBigEndian(data + 4, 4);
|
||||
AVIFINFO_CHECK(width != 0 && height != 0, kInvalid);
|
||||
if (features->num_dim_props < AVIFINFO_MAX_FEATURES &&
|
||||
box_index <= AVIFINFO_MAX_VALUE) {
|
||||
features->dim_props[features->num_dim_props].property_index = box_index;
|
||||
features->dim_props[features->num_dim_props].width = width;
|
||||
features->dim_props[features->num_dim_props].height = height;
|
||||
++features->num_dim_props;
|
||||
} else {
|
||||
features->data_was_skipped = 1;
|
||||
}
|
||||
AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size - 8));
|
||||
} else if (!memcmp(box.type, "pixi", 4)) {
|
||||
// See ISO/IEC 23008-12:2017(E) 6.5.6.2
|
||||
const uint8_t* data;
|
||||
AVIFINFO_CHECK(box.content_size >= 1, kInvalid);
|
||||
AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 1, &data));
|
||||
const uint32_t num_channels = AvifInfoInternalReadBigEndian(data + 0, 1);
|
||||
AVIFINFO_CHECK(num_channels >= 1, kInvalid);
|
||||
AVIFINFO_CHECK(box.content_size >= 1 + num_channels, kInvalid);
|
||||
AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 1, &data));
|
||||
const uint32_t bit_depth = AvifInfoInternalReadBigEndian(data, 1);
|
||||
AVIFINFO_CHECK(bit_depth >= 1, kInvalid);
|
||||
for (uint32_t i = 1; i < num_channels; ++i) {
|
||||
AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 1, &data));
|
||||
// Bit depth should be the same for all channels.
|
||||
AVIFINFO_CHECK(AvifInfoInternalReadBigEndian(data, 1) == bit_depth,
|
||||
kInvalid);
|
||||
AVIFINFO_CHECK(i <= 32, kAborted); // Be reasonable.
|
||||
}
|
||||
if (features->num_chan_props < AVIFINFO_MAX_FEATURES &&
|
||||
box_index <= AVIFINFO_MAX_VALUE && bit_depth <= AVIFINFO_MAX_VALUE &&
|
||||
num_channels <= AVIFINFO_MAX_VALUE) {
|
||||
features->chan_props[features->num_chan_props].property_index =
|
||||
box_index;
|
||||
features->chan_props[features->num_chan_props].bit_depth = bit_depth;
|
||||
features->chan_props[features->num_chan_props].num_channels =
|
||||
num_channels;
|
||||
++features->num_chan_props;
|
||||
} else {
|
||||
features->data_was_skipped = 1;
|
||||
}
|
||||
AVIFINFO_CHECK_FOUND(
|
||||
AvifInfoInternalSkip(stream, box.content_size - (1 + num_channels)));
|
||||
} else if (!memcmp(box.type, "av1C", 4)) {
|
||||
// See AV1 Codec ISO Media File Format Binding 2.3.1
|
||||
// at https://aomediacodec.github.io/av1-isobmff/#av1c
|
||||
// Only parse the necessary third byte. Assume that the others are valid.
|
||||
const uint8_t* data;
|
||||
AVIFINFO_CHECK(box.content_size >= 3, kInvalid);
|
||||
AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 3, &data));
|
||||
const int high_bitdepth = (data[2] & 0x40) != 0;
|
||||
const int twelve_bit = (data[2] & 0x20) != 0;
|
||||
const int monochrome = (data[2] & 0x10) != 0;
|
||||
if (twelve_bit) {
|
||||
AVIFINFO_CHECK(high_bitdepth, kInvalid);
|
||||
}
|
||||
if (features->num_chan_props < AVIFINFO_MAX_FEATURES &&
|
||||
box_index <= AVIFINFO_MAX_VALUE) {
|
||||
features->chan_props[features->num_chan_props].property_index =
|
||||
box_index;
|
||||
features->chan_props[features->num_chan_props].bit_depth =
|
||||
high_bitdepth ? twelve_bit ? 12 : 10 : 8;
|
||||
features->chan_props[features->num_chan_props].num_channels =
|
||||
monochrome ? 1 : 3;
|
||||
++features->num_chan_props;
|
||||
} else {
|
||||
features->data_was_skipped = 1;
|
||||
}
|
||||
AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size - 3));
|
||||
} else if (!memcmp(box.type, "auxC", 4)) {
|
||||
// See AV1 Image File Format (AVIF) 4
|
||||
// at https://aomediacodec.github.io/av1-avif/#auxiliary-images
|
||||
const char* kAlphaStr = "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha";
|
||||
const uint32_t kAlphaStrLength = 44; // Includes terminating character.
|
||||
if (box.content_size >= kAlphaStrLength) {
|
||||
const uint8_t* data;
|
||||
AVIFINFO_CHECK_FOUND(
|
||||
AvifInfoInternalRead(stream, kAlphaStrLength, &data));
|
||||
const char* const aux_type = (const char*)data;
|
||||
if (strcmp(aux_type, kAlphaStr) == 0) {
|
||||
// Note: It is unlikely but it is possible that this alpha plane does
|
||||
// not belong to the primary item or a tile. Ignore this issue.
|
||||
features->has_alpha = 1;
|
||||
}
|
||||
AVIFINFO_CHECK_FOUND(
|
||||
AvifInfoInternalSkip(stream, box.content_size - kAlphaStrLength));
|
||||
} else {
|
||||
AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
|
||||
}
|
||||
} else {
|
||||
AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
|
||||
}
|
||||
++box_index;
|
||||
num_remaining_bytes -= box.size;
|
||||
} while (num_remaining_bytes > 0);
|
||||
AVIFINFO_RETURN(kNotFound);
|
||||
}
|
||||
|
||||
// Parses a 'stream' of an "iprp" box into 'features'. The "ipco" box contain
|
||||
// the properties which are linked to items by the "ipma" box.
|
||||
static AvifInfoInternalStatus ParseIprp(AvifInfoInternalStream* stream,
|
||||
uint32_t num_remaining_bytes,
|
||||
uint32_t* num_parsed_boxes,
|
||||
AvifInfoInternalFeatures* features) {
|
||||
do {
|
||||
AvifInfoInternalBox box;
|
||||
AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, num_remaining_bytes,
|
||||
num_parsed_boxes, &box));
|
||||
|
||||
if (!memcmp(box.type, "ipco", 4)) {
|
||||
AVIFINFO_CHECK_NOT_FOUND(
|
||||
ParseIpco(stream, box.content_size, num_parsed_boxes, features));
|
||||
} else if (!memcmp(box.type, "ipma", 4)) {
|
||||
// See ISO/IEC 23008-12:2017(E) 9.3.2
|
||||
uint32_t num_read_bytes = 4;
|
||||
const uint8_t* data;
|
||||
AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid);
|
||||
AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &data));
|
||||
const uint32_t entry_count = AvifInfoInternalReadBigEndian(data, 4);
|
||||
const uint32_t id_num_bytes = (box.version < 1) ? 2 : 4;
|
||||
const uint32_t index_num_bytes = (box.flags & 1) ? 2 : 1;
|
||||
const uint32_t essential_bit_mask = (box.flags & 1) ? 0x8000 : 0x80;
|
||||
|
||||
for (uint32_t entry = 0; entry < entry_count; ++entry) {
|
||||
if (entry >= AVIFINFO_MAX_PROPS ||
|
||||
features->num_props >= AVIFINFO_MAX_PROPS) {
|
||||
features->data_was_skipped = 1;
|
||||
break;
|
||||
}
|
||||
num_read_bytes += id_num_bytes + 1;
|
||||
AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid);
|
||||
AVIFINFO_CHECK_FOUND(
|
||||
AvifInfoInternalRead(stream, id_num_bytes + 1, &data));
|
||||
const uint32_t item_id =
|
||||
AvifInfoInternalReadBigEndian(data, id_num_bytes);
|
||||
const uint32_t association_count =
|
||||
AvifInfoInternalReadBigEndian(data + id_num_bytes, 1);
|
||||
|
||||
uint32_t property;
|
||||
for (property = 0; property < association_count; ++property) {
|
||||
if (property >= AVIFINFO_MAX_PROPS ||
|
||||
features->num_props >= AVIFINFO_MAX_PROPS) {
|
||||
features->data_was_skipped = 1;
|
||||
break;
|
||||
}
|
||||
num_read_bytes += index_num_bytes;
|
||||
AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid);
|
||||
AVIFINFO_CHECK_FOUND(
|
||||
AvifInfoInternalRead(stream, index_num_bytes, &data));
|
||||
const uint32_t value =
|
||||
AvifInfoInternalReadBigEndian(data, index_num_bytes);
|
||||
// const int essential = (value & essential_bit_mask); // Unused.
|
||||
const uint32_t property_index = (value & ~essential_bit_mask);
|
||||
if (property_index <= AVIFINFO_MAX_VALUE &&
|
||||
item_id <= AVIFINFO_MAX_VALUE) {
|
||||
features->props[features->num_props].property_index =
|
||||
property_index;
|
||||
features->props[features->num_props].item_id = item_id;
|
||||
++features->num_props;
|
||||
} else {
|
||||
features->data_was_skipped = 1;
|
||||
}
|
||||
}
|
||||
if (property < association_count) break; // Do not read garbage.
|
||||
}
|
||||
|
||||
// If all features are available now, do not look further.
|
||||
AVIFINFO_CHECK_NOT_FOUND(
|
||||
AvifInfoInternalGetPrimaryItemFeatures(features));
|
||||
|
||||
AVIFINFO_CHECK_FOUND(
|
||||
AvifInfoInternalSkip(stream, box.content_size - num_read_bytes));
|
||||
} else {
|
||||
AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
|
||||
}
|
||||
num_remaining_bytes -= box.size;
|
||||
} while (num_remaining_bytes != 0);
|
||||
AVIFINFO_RETURN(kNotFound);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Parses a 'stream' of an "iref" box into 'features'.
|
||||
// The "dimg" boxes contain links between tiles and their parent items, which
|
||||
// can be used to infer bit depth and number of channels for the primary item
|
||||
// when the latter does not have these properties.
|
||||
static AvifInfoInternalStatus ParseIref(AvifInfoInternalStream* stream,
|
||||
uint32_t num_remaining_bytes,
|
||||
uint32_t* num_parsed_boxes,
|
||||
AvifInfoInternalFeatures* features) {
|
||||
do {
|
||||
AvifInfoInternalBox box;
|
||||
AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, num_remaining_bytes,
|
||||
num_parsed_boxes, &box));
|
||||
|
||||
if (!memcmp(box.type, "dimg", 4)) {
|
||||
// See ISO/IEC 14496-12:2015(E) 8.11.12.2
|
||||
const uint32_t num_bytes_per_id = (box.version == 0) ? 2 : 4;
|
||||
uint32_t num_read_bytes = num_bytes_per_id + 2;
|
||||
const uint8_t* data;
|
||||
AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid);
|
||||
AVIFINFO_CHECK_FOUND(
|
||||
AvifInfoInternalRead(stream, num_bytes_per_id + 2, &data));
|
||||
const uint32_t from_item_id =
|
||||
AvifInfoInternalReadBigEndian(data, num_bytes_per_id);
|
||||
const uint32_t reference_count =
|
||||
AvifInfoInternalReadBigEndian(data + num_bytes_per_id, 2);
|
||||
|
||||
for (uint32_t i = 0; i < reference_count; ++i) {
|
||||
if (i >= AVIFINFO_MAX_TILES) {
|
||||
features->data_was_skipped = 1;
|
||||
break;
|
||||
}
|
||||
num_read_bytes += num_bytes_per_id;
|
||||
AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid);
|
||||
AVIFINFO_CHECK_FOUND(
|
||||
AvifInfoInternalRead(stream, num_bytes_per_id, &data));
|
||||
const uint32_t to_item_id =
|
||||
AvifInfoInternalReadBigEndian(data, num_bytes_per_id);
|
||||
if (from_item_id <= AVIFINFO_MAX_VALUE &&
|
||||
to_item_id <= AVIFINFO_MAX_VALUE &&
|
||||
features->num_tiles < AVIFINFO_MAX_TILES) {
|
||||
features->tiles[features->num_tiles].tile_item_id = to_item_id;
|
||||
features->tiles[features->num_tiles].parent_item_id = from_item_id;
|
||||
++features->num_tiles;
|
||||
} else {
|
||||
features->data_was_skipped = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// If all features are available now, do not look further.
|
||||
AVIFINFO_CHECK_NOT_FOUND(
|
||||
AvifInfoInternalGetPrimaryItemFeatures(features));
|
||||
} else {
|
||||
AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
|
||||
}
|
||||
num_remaining_bytes -= box.size;
|
||||
} while (num_remaining_bytes > 0);
|
||||
AVIFINFO_RETURN(kNotFound);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Parses a 'stream' of a "meta" box. It looks for the primary item ID in the
|
||||
// "pitm" box and recurses into other boxes to find its 'features'.
|
||||
static AvifInfoInternalStatus ParseMeta(AvifInfoInternalStream* stream,
|
||||
uint32_t num_remaining_bytes,
|
||||
uint32_t* num_parsed_boxes,
|
||||
AvifInfoInternalFeatures* features) {
|
||||
do {
|
||||
AvifInfoInternalBox box;
|
||||
AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, num_remaining_bytes,
|
||||
num_parsed_boxes, &box));
|
||||
|
||||
if (!memcmp(box.type, "pitm", 4)) {
|
||||
// See ISO/IEC 14496-12:2015(E) 8.11.4.2
|
||||
const uint32_t num_bytes_per_id = (box.version == 0) ? 2 : 4;
|
||||
const uint8_t* data;
|
||||
AVIFINFO_CHECK(num_bytes_per_id <= num_remaining_bytes, kInvalid);
|
||||
AVIFINFO_CHECK_FOUND(
|
||||
AvifInfoInternalRead(stream, num_bytes_per_id, &data));
|
||||
const uint32_t primary_item_id =
|
||||
AvifInfoInternalReadBigEndian(data, num_bytes_per_id);
|
||||
AVIFINFO_CHECK(primary_item_id <= AVIFINFO_MAX_VALUE, kAborted);
|
||||
features->has_primary_item = 1;
|
||||
features->primary_item_id = primary_item_id;
|
||||
AVIFINFO_CHECK_FOUND(
|
||||
AvifInfoInternalSkip(stream, box.content_size - num_bytes_per_id));
|
||||
} else if (!memcmp(box.type, "iprp", 4)) {
|
||||
AVIFINFO_CHECK_NOT_FOUND(
|
||||
ParseIprp(stream, box.content_size, num_parsed_boxes, features));
|
||||
} else if (!memcmp(box.type, "iref", 4)) {
|
||||
AVIFINFO_CHECK_NOT_FOUND(
|
||||
ParseIref(stream, box.content_size, num_parsed_boxes, features));
|
||||
} else {
|
||||
AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
|
||||
}
|
||||
num_remaining_bytes -= box.size;
|
||||
} while (num_remaining_bytes != 0);
|
||||
// According to ISO/IEC 14496-12:2012(E) 8.11.1.1 there is at most one "meta".
|
||||
AVIFINFO_RETURN(features->data_was_skipped ? kAborted : kInvalid);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Parses a file 'stream'. The file type is checked through the "ftyp" box.
|
||||
static AvifInfoInternalStatus ParseFtyp(AvifInfoInternalStream* stream) {
|
||||
AvifInfoInternalBox box;
|
||||
uint32_t num_parsed_boxes = 0;
|
||||
AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, AVIFINFO_MAX_SIZE,
|
||||
&num_parsed_boxes, &box));
|
||||
AVIFINFO_CHECK(!memcmp(box.type, "ftyp", 4), kInvalid);
|
||||
// Iterate over brands. See ISO/IEC 14496-12:2012(E) 4.3.1
|
||||
AVIFINFO_CHECK(box.content_size >= 8, kInvalid); // major_brand,minor_version
|
||||
for (uint32_t i = 0; i + 4 <= box.content_size; i += 4) {
|
||||
const uint8_t* data;
|
||||
AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &data));
|
||||
if (i == 4) continue; // Skip minor_version.
|
||||
if (!memcmp(data, "avif", 4) || !memcmp(data, "avis", 4)) {
|
||||
AVIFINFO_CHECK_FOUND(
|
||||
AvifInfoInternalSkip(stream, box.content_size - (i + 4)));
|
||||
return kFound;
|
||||
}
|
||||
AVIFINFO_CHECK(i <= 32 * 4, kAborted); // Be reasonable.
|
||||
}
|
||||
AVIFINFO_RETURN(kInvalid); // No AVIF brand no good.
|
||||
}
|
||||
|
||||
// Parses a file 'stream'. 'features' are extracted from the "meta" box.
|
||||
static AvifInfoInternalStatus ParseFile(AvifInfoInternalStream* stream,
|
||||
uint32_t* num_parsed_boxes,
|
||||
AvifInfoInternalFeatures* features) {
|
||||
while (1) {
|
||||
AvifInfoInternalBox box;
|
||||
AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, AVIFINFO_MAX_SIZE,
|
||||
num_parsed_boxes, &box));
|
||||
if (!memcmp(box.type, "meta", 4)) {
|
||||
return ParseMeta(stream, box.content_size, num_parsed_boxes, features);
|
||||
} else {
|
||||
AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
|
||||
}
|
||||
}
|
||||
AVIFINFO_RETURN(kInvalid); // No "meta" no good.
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers for converting the fixed-size input public API to the streamed one.
|
||||
|
||||
typedef struct {
|
||||
const uint8_t* data;
|
||||
size_t data_size;
|
||||
} AvifInfoInternalForward;
|
||||
|
||||
static const uint8_t* AvifInfoInternalForwardRead(void* stream,
|
||||
size_t num_bytes) {
|
||||
AvifInfoInternalForward* forward = (AvifInfoInternalForward*)stream;
|
||||
if (num_bytes > forward->data_size) return NULL;
|
||||
const uint8_t* data = forward->data;
|
||||
forward->data += num_bytes;
|
||||
forward->data_size -= num_bytes;
|
||||
return data;
|
||||
}
|
||||
|
||||
static void AvifInfoInternalForwardSkip(void* stream, size_t num_bytes) {
|
||||
AvifInfoInternalForward* forward = (AvifInfoInternalForward*)stream;
|
||||
if (num_bytes > forward->data_size) num_bytes = forward->data_size;
|
||||
forward->data += num_bytes;
|
||||
forward->data_size -= num_bytes;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Fixed-size input public API
|
||||
|
||||
AvifInfoStatus AvifInfoIdentify(const uint8_t* data, size_t data_size) {
|
||||
AvifInfoInternalForward stream;
|
||||
stream.data = data;
|
||||
stream.data_size = data_size;
|
||||
// Forward null 'data' as a null 'stream' to handle it the same way.
|
||||
return AvifInfoIdentifyStream(
|
||||
(void*)&stream, (data == NULL) ? NULL : AvifInfoInternalForwardRead,
|
||||
AvifInfoInternalForwardSkip);
|
||||
}
|
||||
|
||||
AvifInfoStatus AvifInfoGetFeatures(const uint8_t* data, size_t data_size,
|
||||
AvifInfoFeatures* features) {
|
||||
AvifInfoInternalForward stream;
|
||||
stream.data = data;
|
||||
stream.data_size = data_size;
|
||||
return AvifInfoGetFeaturesStream(
|
||||
(void*)&stream, (data == NULL) ? NULL : AvifInfoInternalForwardRead,
|
||||
AvifInfoInternalForwardSkip, features);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Streamed input API
|
||||
|
||||
AvifInfoStatus AvifInfoIdentifyStream(void* stream, read_stream_t read,
|
||||
skip_stream_t skip) {
|
||||
if (read == NULL) return kAvifInfoNotEnoughData;
|
||||
|
||||
AvifInfoInternalStream internal_stream;
|
||||
internal_stream.stream = stream;
|
||||
internal_stream.read = read;
|
||||
internal_stream.skip = skip; // Fallbacks to 'read' if null.
|
||||
return AvifInfoInternalConvertStatus(ParseFtyp(&internal_stream));
|
||||
}
|
||||
|
||||
AvifInfoStatus AvifInfoGetFeaturesStream(void* stream, read_stream_t read,
|
||||
skip_stream_t skip,
|
||||
AvifInfoFeatures* features) {
|
||||
if (features != NULL) memset(features, 0, sizeof(*features));
|
||||
if (read == NULL) return kAvifInfoNotEnoughData;
|
||||
|
||||
AvifInfoInternalStream internal_stream;
|
||||
internal_stream.stream = stream;
|
||||
internal_stream.read = read;
|
||||
internal_stream.skip = skip; // Fallbacks to 'read' if null.
|
||||
uint32_t num_parsed_boxes = 0;
|
||||
AvifInfoInternalFeatures internal_features;
|
||||
memset(&internal_features, AVIFINFO_UNDEFINED, sizeof(internal_features));
|
||||
|
||||
// Go through all relevant boxes sequentially.
|
||||
const AvifInfoInternalStatus status =
|
||||
ParseFile(&internal_stream, &num_parsed_boxes, &internal_features);
|
||||
if (status == kFound && features != NULL) {
|
||||
memcpy(features, &internal_features.primary_item_features,
|
||||
sizeof(*features));
|
||||
}
|
||||
return AvifInfoInternalConvertStatus(status);
|
||||
}
|
92
ext/standard/libavifinfo/avifinfo.h
Normal file
92
ext/standard/libavifinfo/avifinfo.h
Normal file
|
@ -0,0 +1,92 @@
|
|||
// Copyright (c) 2021, Alliance for Open Media. All rights reserved
|
||||
//
|
||||
// This source code is subject to the terms of the BSD 2 Clause License and
|
||||
// the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
|
||||
// was not distributed with this source code in the LICENSE file, you can
|
||||
// obtain it at www.aomedia.org/license/software. If the Alliance for Open
|
||||
// Media Patent License 1.0 was not distributed with this source code in the
|
||||
// PATENTS file, you can obtain it at www.aomedia.org/license/patent.
|
||||
|
||||
#ifndef AVIFINFO_H_
|
||||
#define AVIFINFO_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
typedef enum {
|
||||
kAvifInfoOk, // The file was correctly parsed and the requested
|
||||
// information was extracted. It is not guaranteed
|
||||
// that the input bitstream is a valid complete
|
||||
// AVIF file.
|
||||
kAvifInfoNotEnoughData, // The input bitstream was correctly parsed until
|
||||
// now but bytes are missing. The request should be
|
||||
// repeated with more input bytes.
|
||||
kAvifInfoTooComplex, // The input bitstream was correctly parsed until
|
||||
// now but it is too complex. The parsing was
|
||||
// stopped to avoid any timeout or crash.
|
||||
kAvifInfoInvalidFile, // The input bitstream is not a valid AVIF file,
|
||||
// truncated or not.
|
||||
} AvifInfoStatus;
|
||||
|
||||
typedef struct {
|
||||
uint32_t width, height; // In number of pixels. Ignores mirror and rotation.
|
||||
uint32_t bit_depth; // Likely 8, 10 or 12 bits per channel per pixel.
|
||||
uint32_t num_channels; // Likely 1, 2, 3 or 4 channels:
|
||||
// (1 monochrome or 3 colors) + (0 or 1 alpha)
|
||||
} AvifInfoFeatures;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Fixed-size input API
|
||||
// Use this API if a raw byte array of fixed size is available as input.
|
||||
|
||||
// Parses the 'data' and returns kAvifInfoOk if it is identified as an AVIF.
|
||||
// The file type can be identified in the first 12 bytes of most AVIF files.
|
||||
AvifInfoStatus AvifInfoIdentify(const uint8_t* data, size_t data_size);
|
||||
|
||||
// Parses the identified AVIF 'data' and extracts its 'features'.
|
||||
// 'data' can be partial but must point to the beginning of the AVIF file.
|
||||
// The 'features' can be parsed in the first 450 bytes of most AVIF files.
|
||||
// 'features' are set to 0 unless kAvifInfoOk is returned.
|
||||
AvifInfoStatus AvifInfoGetFeatures(const uint8_t* data, size_t data_size,
|
||||
AvifInfoFeatures* features);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Streamed input API
|
||||
// Use this API if the input bytes must be fetched and/or if the AVIF payload
|
||||
// size is unknown. Implement the two function signatures below and pass them to
|
||||
// AvifInfoRead*() with a 'stream', which can be anything (file, struct etc.).
|
||||
|
||||
// Reads 'num_bytes' from the 'stream'.
|
||||
// The position in the 'stream' must be advanced by 'num_bytes'.
|
||||
// Returns a pointer to the 'num_bytes' or null if it cannot be fulfilled.
|
||||
// The returned data must remain valid until the next read.
|
||||
typedef const uint8_t* (*read_stream_t)(void* stream, size_t num_bytes);
|
||||
// Advances the position in the 'stream' by 'num_bytes'.
|
||||
typedef void (*skip_stream_t)(void* stream, size_t num_bytes);
|
||||
|
||||
// Maximum number of bytes requested per read. There is no limit per skip.
|
||||
#define AVIFINFO_MAX_NUM_READ_BYTES 64
|
||||
|
||||
// Same as AvifInfo*() but takes a 'stream' as input. AvifInfo*Stream() does
|
||||
// not access the 'stream' directly but passes it as is to 'read' and 'skip'.
|
||||
// 'read' cannot be null. If 'skip' is null, 'read' is called instead.
|
||||
AvifInfoStatus AvifInfoIdentifyStream(void* stream, read_stream_t read,
|
||||
skip_stream_t skip);
|
||||
// Can be called right after AvifInfoIdentifyStream() with the same 'stream'.
|
||||
AvifInfoStatus AvifInfoGetFeaturesStream(void* stream, read_stream_t read,
|
||||
skip_stream_t skip,
|
||||
AvifInfoFeatures* features);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // AVIFINFO_H_
|
|
@ -70,15 +70,19 @@ array(17) {
|
|||
string(9) "image/bmp"
|
||||
}
|
||||
["test1pix.avif"]=>
|
||||
array(5) {
|
||||
array(7) {
|
||||
[0]=>
|
||||
int(0)
|
||||
int(102)
|
||||
[1]=>
|
||||
int(0)
|
||||
int(121)
|
||||
[2]=>
|
||||
int(19)
|
||||
[3]=>
|
||||
string(20) "width="0" height="0""
|
||||
string(24) "width="102" height="121""
|
||||
["bits"]=>
|
||||
int(8)
|
||||
["channels"]=>
|
||||
int(4)
|
||||
["mime"]=>
|
||||
string(10) "image/avif"
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue