From b96bb09409a67f8e7c2eafc2aedfa95986a6bd18 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Tue, 13 Jun 2023 15:59:39 -0600 Subject: [PATCH 01/79] Add AVIF support --- src/js/_enqueues/vendor/plupload/handlers.js | 5 + .../_enqueues/vendor/plupload/wp-plupload.js | 5 + src/js/_enqueues/vendor/thickbox/thickbox.js | 5 +- src/js/media/controllers/library.js | 2 +- src/wp-admin/includes/image-edit.php | 11 + src/wp-admin/includes/media.php | 5 + src/wp-admin/includes/schema.php | 1 + src/wp-includes/class-avif-info.php | 775 ++++++++++++++++++ src/wp-includes/class-wp-image-editor-gd.php | 24 +- .../class-wp-image-editor-imagick.php | 9 + src/wp-includes/class-wp-image-editor.php | 1 + src/wp-includes/class-wp-theme.php | 2 +- .../class-wp-customize-media-control.php | 2 +- src/wp-includes/deprecated.php | 6 +- src/wp-includes/formatting.php | 2 +- src/wp-includes/functions.php | 20 +- src/wp-includes/media.php | 94 +++ src/wp-includes/post.php | 2 +- .../class-wp-rest-attachments-controller.php | 2 +- 19 files changed, 961 insertions(+), 12 deletions(-) create mode 100644 src/wp-includes/class-avif-info.php diff --git a/src/js/_enqueues/vendor/plupload/handlers.js b/src/js/_enqueues/vendor/plupload/handlers.js index e4ff962c9762e..8a31143b8f094 100644 --- a/src/js/_enqueues/vendor/plupload/handlers.js +++ b/src/js/_enqueues/vendor/plupload/handlers.js @@ -610,6 +610,11 @@ jQuery( document ).ready( function( $ ) { wpQueueError( pluploadL10n.noneditable_image ); up.removeFile( file ); return; + } else if ( file.type === 'image/avif' && up.settings.avif_upload_error ) { + // Disallow uploading of AVIF images if the server cannot edit them. + wpQueueError( pluploadL10n.noneditable_image ); + up.removeFile( file ); + return; } fileQueued( file ); diff --git a/src/js/_enqueues/vendor/plupload/wp-plupload.js b/src/js/_enqueues/vendor/plupload/wp-plupload.js index 0fdebf77d1858..c0eb570657bf4 100644 --- a/src/js/_enqueues/vendor/plupload/wp-plupload.js +++ b/src/js/_enqueues/vendor/plupload/wp-plupload.js @@ -363,6 +363,11 @@ window.wp = window.wp || {}; error( pluploadL10n.noneditable_image, {}, file, 'no-retry' ); up.removeFile( file ); return; + } else if ( file.type === 'image/avif' && up.settings.avif_upload_error ) { + // Disallow uploading of AVIF images if the server cannot edit them. + error( pluploadL10n.noneditable_image, {}, file, 'no-retry' ); + up.removeFile( file ); + return; } // Generate attributes for a new `Attachment` model. diff --git a/src/js/_enqueues/vendor/thickbox/thickbox.js b/src/js/_enqueues/vendor/thickbox/thickbox.js index 5470467a1e0a8..e8b95677c1ad4 100644 --- a/src/js/_enqueues/vendor/thickbox/thickbox.js +++ b/src/js/_enqueues/vendor/thickbox/thickbox.js @@ -76,7 +76,7 @@ function tb_show(caption, url, imageGroup) {//function called when the user clic baseURL = url; } - var urlString = /\.jpg$|\.jpeg$|\.png$|\.gif$|\.bmp$|\.webp$/; + var urlString = /\.jpg$|\.jpeg$|\.png$|\.gif$|\.bmp$|\.webp$|\.avif$/; var urlType = baseURL.toLowerCase().match(urlString); if(urlType == '.jpg' || @@ -84,7 +84,8 @@ function tb_show(caption, url, imageGroup) {//function called when the user clic urlType == '.png' || urlType == '.gif' || urlType == '.bmp' || - urlType == '.webp' + urlType == '.webp' || + urlType == '.avif' ){//code to show images TB_PrevCaption = ""; diff --git a/src/js/media/controllers/library.js b/src/js/media/controllers/library.js index 2acc89a58692e..126ce8d7837fb 100644 --- a/src/js/media/controllers/library.js +++ b/src/js/media/controllers/library.js @@ -196,7 +196,7 @@ Library = wp.media.controller.State.extend(/** @lends wp.media.controller.Librar isImageAttachment: function( attachment ) { // If uploading, we know the filename but not the mime type. if ( attachment.get('uploading') ) { - return /\.(jpe?g|png|gif|webp)$/i.test( attachment.get('filename') ); + return /\.(jpe?g|png|gif|webp|avif)$/i.test( attachment.get('filename') ); } return attachment.get('type') === 'image'; diff --git a/src/wp-admin/includes/image-edit.php b/src/wp-admin/includes/image-edit.php index 0f7257b3b6a63..7f39d7d62a4c1 100644 --- a/src/wp-admin/includes/image-edit.php +++ b/src/wp-admin/includes/image-edit.php @@ -387,6 +387,12 @@ function wp_stream_image( $image, $mime_type, $attachment_id ) { return imagewebp( $image, null, 90 ); } return false; + case 'image/avif': + if ( function_exists( 'imageavif' ) ) { + header( 'Content-Type: image/avif' ); + return imageavif( $image, null, 90 ); + } + return false; default: return false; } @@ -491,6 +497,11 @@ function wp_save_image_file( $filename, $image, $mime_type, $post_id ) { return imagewebp( $image, $filename ); } return false; + case 'image/avif': + if ( function_exists( 'imageavif' ) ) { + return imageavif( $image, $filename ); + } + return false; default: return false; } diff --git a/src/wp-admin/includes/media.php b/src/wp-admin/includes/media.php index ea3c8567825be..bbc97f1363e5a 100644 --- a/src/wp-admin/includes/media.php +++ b/src/wp-admin/includes/media.php @@ -2194,6 +2194,11 @@ function media_upload_form( $errors = null ) { $plupload_init['webp_upload_error'] = true; } + // Check if AVIF images can be edited. + if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/avif' ) ) ) { + $plupload_init['avif_upload_error'] = true; + } + /** * Filters the default Plupload settings. * diff --git a/src/wp-admin/includes/schema.php b/src/wp-admin/includes/schema.php index 8cb50fe971886..da01b8b10c210 100644 --- a/src/wp-admin/includes/schema.php +++ b/src/wp-admin/includes/schema.php @@ -1247,6 +1247,7 @@ function populate_network_meta( $network_id, array $meta = array() ) { 'png', 'gif', 'webp', + 'avif', // Video. 'mov', 'avi', diff --git a/src/wp-includes/class-avif-info.php b/src/wp-includes/class-avif-info.php new file mode 100644 index 0000000000000..84a2e9333de08 --- /dev/null +++ b/src/wp-includes/class-avif-info.php @@ -0,0 +1,775 @@ += 2^31 on 32-bit systems. + // See https://www.php.net/manual/en/function.unpack.php#106041 + return unpack( 'N', $input ) [1]; + } +} + +/** + * Reads bytes and advances the stream position by the same count. + * + * @param stream $handle Bytes will be read from this resource. + * @param int $num_bytes Number of bytes read. Must be greater than 0. + * @return binary string|false The raw bytes or false on failure. + */ +function read( $handle, $num_bytes ) { + $data = fread( $handle, $num_bytes ); + return ( $data !== false && strlen( $data ) >= $num_bytes ) ? $data : false; +} + +/** + * Advances the stream position by the given offset. + * + * @param stream $handle Bytes will be skipped from this resource. + * @param int $num_bytes Number of skipped bytes. Can be 0. + * @return bool True on success or false on failure. + */ +// Skips 'num_bytes' from the 'stream'. 'num_bytes' can be zero. +function skip( $handle, $num_bytes ) { + return ( fseek( $handle, $num_bytes, SEEK_CUR ) == 0 ); +} + +//------------------------------------------------------------------------------ +// Features are parsed into temporary property associations. + +class Tile { // Tile item id <-> parent item id associations. + public $tile_item_id; + public $parent_item_id; +} + +class Prop { // Property index <-> item id associations. + public $property_index; + public $item_id; +} + +class Dim_Prop { // Property <-> features associations. + public $property_index; + public $width; + public $height; +} + +class Chan_Prop { // Property <-> features associations. + public $property_index; + public $bit_depth; + public $num_channels; +} + +class Features { + public $has_primary_item = false; // True if "pitm" was parsed. + public $has_alpha = false; // True if an alpha "auxC" was parsed. + public $primary_item_id; + public $primary_item_features = array( // Deduced from the data below. + 'width' => UNDEFINED, // In number of pixels. + 'height' => UNDEFINED, // Ignores mirror and rotation. + 'bit_depth' => UNDEFINED, // Likely 8, 10 or 12 bits per channel per pixel. + 'num_channels' => UNDEFINED // Likely 1, 2, 3 or 4 channels: + // (1 monochrome or 3 colors) + (0 or 1 alpha) + ); + + public $tiles = array(); // Tile[] + public $props = array(); // Prop[] + public $dim_props = array(); // Dim_Prop[] + public $chan_props = array(); // Chan_Prop[] + + /** + * Binds the width, height, bit depth and number of channels from stored internal features. + * + * @param int $target_item_id Id of the item whose features will be bound. + * @param int $tile_depth Maximum recursion to search within tile-parent relations. + * @return Status FOUND on success or NOT_FOUND on failure. + */ + private function get_item_features( $target_item_id, $tile_depth ) { + foreach ( $this->props as $prop ) { + if ( $prop->item_id != $target_item_id ) { + continue; + } + + // Retrieve the width and height of the primary item if not already done. + if ( $target_item_id == $this->primary_item_id && + ( $this->primary_item_features['width'] == UNDEFINED || + $this->primary_item_features['height'] == UNDEFINED ) ) { + foreach ( $this->dim_props as $dim_prop ) { + if ( $dim_prop->property_index != $prop->property_index ) { + continue; + } + $this->primary_item_features['width'] = $dim_prop->width; + $this->primary_item_features['height'] = $dim_prop->height; + if ( $this->primary_item_features['bit_depth'] != UNDEFINED && + $this->primary_item_features['num_channels'] != UNDEFINED ) { + return FOUND; + } + break; + } + } + // Retrieve the bit depth and number of channels of the target item if not + // already done. + if ( $this->primary_item_features['bit_depth'] == UNDEFINED || + $this->primary_item_features['num_channels'] == UNDEFINED ) { + foreach ( $this->chan_props as $chan_prop ) { + if ( $chan_prop->property_index != $prop->property_index ) { + continue; + } + $this->primary_item_features['bit_depth'] = $chan_prop->bit_depth; + $this->primary_item_features['num_channels'] = $chan_prop->num_channels; + if ( $this->primary_item_features['width'] != UNDEFINED && + $this->primary_item_features['height'] != UNDEFINED ) { + return FOUND; + } + break; + } + } + } + + // Check for the bit_depth and num_channels in a tile if not yet found. + if ( $tile_depth < 3 ) { + foreach ( $this->tiles as $tile ) { + if ( $tile->parent_item_id != $target_item_id ) { + continue; + } + $status = get_item_features( $tile->tile_item_id, $tile_depth + 1 ); + if ( $status != NOT_FOUND ) { + return $status; + } + } + } + return NOT_FOUND; + } + + /** + * Finds the width, height, bit depth and number of channels of the primary item. + * + * @return Status FOUND on success or NOT_FOUND on failure. + */ + public function get_primary_item_features() { + // Nothing to do without the primary item ID. + if ( !$this->has_primary_item ) { + return NOT_FOUND; + } + // Early exit. + if ( empty( $this->dim_props ) || empty( $this->chan_props ) ) { + return NOT_FOUND; + } + $status = $this->get_item_features( $this->primary_item_id, /*tile_depth=*/ 0 ); + if ( $status != FOUND ) { + return $status; + } + + // "auxC" is parsed before the "ipma" properties so it is known now, if any. + if ( $this->has_alpha ) { + ++$this->primary_item_features['num_channels']; + } + return FOUND; + } +} + +//------------------------------------------------------------------------------ + +class Box { + public $size; // In bytes. + public $type; // Four characters. + public $version; // 0 or actual version if this is a full box. + public $flags; // 0 or actual value if this is a full box. + public $content_size; // 'size' minus the header size. + + /** + * Reads the box header. + * + * @param stream $handle The resource the header will be parsed from. + * @param int $num_parsed_boxes The total number of parsed boxes. Prevents timeouts. + * @param int $num_remaining_bytes The number of bytes that should be available from the resource. + * @return Status FOUND on success or an error on failure. + */ + public function parse( $handle, &$num_parsed_boxes, $num_remaining_bytes = MAX_SIZE ) { + // See ISO/IEC 14496-12:2012(E) 4.2 + $header_size = 8; // box 32b size + 32b type (at least) + if ( $header_size > $num_remaining_bytes ) { + return INVALID; + } + if ( !( $data = read( $handle, 8 ) ) ) { + return TRUNCATED; + } + $this->size = read_big_endian( $data, 4 ); + $this->type = substr( $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 ( $this->size == 1 ) { + $header_size += 8; + if ( $header_size > $num_remaining_bytes ) { + return INVALID; + } + if ( !( $data = read( $handle, 8 ) ) ) { + return TRUNCATED; + } + // Stop the parsing if any box has a size greater than 4GB. + if ( read_big_endian( $data, 4 ) != 0 ) { + return ABORTED; + } + // Read the 32 least-significant bits. + $this->size = read_big_endian( substr( $data, 4, 4 ), 4 ); + } else if ( $this->size == 0 ) { + $this->size = $num_remaining_bytes; + } + if ( $this->size < $header_size ) { + return INVALID; + } + if ( $this->size > $num_remaining_bytes ) { + return INVALID; + } + + $has_fullbox_header = $this->type == 'meta' || $this->type == 'pitm' || + $this->type == 'ipma' || $this->type == 'ispe' || + $this->type == 'pixi' || $this->type == 'iref' || + $this->type == 'auxC'; + if ( $has_fullbox_header ) { + $header_size += 4; + } + if ( $this->size < $header_size ) { + return INVALID; + } + $this->content_size = $this->size - $header_size; + // Avoid timeouts. The maximum number of parsed boxes is arbitrary. + ++$num_parsed_boxes; + if ( $num_parsed_boxes >= MAX_NUM_BOXES ) { + return ABORTED; + } + + $this->version = 0; + $this->flags = 0; + if ( $has_fullbox_header ) { + if ( !( $data = read( $handle, 4 ) ) ) { + return TRUNCATED; + } + $this->version = read_big_endian( $data, 1 ); + $this->flags = read_big_endian( substr( $data, 1, 3 ), 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). + $is_parsable = ( $this->type == 'meta' && $this->version <= 0 ) || + ( $this->type == 'pitm' && $this->version <= 1 ) || + ( $this->type == 'ipma' && $this->version <= 1 ) || + ( $this->type == 'ispe' && $this->version <= 0 ) || + ( $this->type == 'pixi' && $this->version <= 0 ) || + ( $this->type == 'iref' && $this->version <= 1 ) || + ( $this->type == 'auxC' && $this->version <= 0 ); + // Instead of considering this file as invalid, skip unparsable boxes. + if ( !$is_parsable ) { + $this->type = 'unknownversion'; + } + } + // print_r( $this ); // Uncomment to print all boxes. + return FOUND; + } +} + +//------------------------------------------------------------------------------ + +class Parser { + private $handle; // Input stream. + private $num_parsed_boxes = 0; + private $data_was_skipped = false; + public $features; + + function __construct( $handle ) { + $this->handle = $handle; + $this->features = new Features(); + } + + /** + * Parses an "ipco" box. + * + * "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. + * + * @param stream $handle The resource the box will be parsed from. + * @param int $num_remaining_bytes The number of bytes that should be available from the resource. + * @return Status FOUND on success or an error on failure. + */ + private function parse_ipco( $num_remaining_bytes ) { + $box_index = 1; // 1-based index. Used for iterating over properties. + do { + $box = new Box(); + $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); + if ( $status != FOUND ) { + return $status; + } + + if ( $box->type == 'ispe' ) { + // See ISO/IEC 23008-12:2017(E) 6.5.3.2 + if ( $box->content_size < 8 ) { + return INVALID; + } + if ( !( $data = read( $this->handle, 8 ) ) ) { + return TRUNCATED; + } + $width = read_big_endian( substr( $data, 0, 4 ), 4 ); + $height = read_big_endian( substr( $data, 4, 4 ), 4 ); + if ( $width == 0 || $height == 0 ) { + return INVALID; + } + if ( count( $this->features->dim_props ) <= MAX_FEATURES && + $box_index <= MAX_VALUE ) { + $dim_prop_count = count( $this->features->dim_props ); + $this->features->dim_props[$dim_prop_count] = new Dim_Prop(); + $this->features->dim_props[$dim_prop_count]->property_index = $box_index; + $this->features->dim_props[$dim_prop_count]->width = $width; + $this->features->dim_props[$dim_prop_count]->height = $height; + } else { + $this->data_was_skipped = true; + } + if ( !skip( $this->handle, $box->content_size - 8 ) ) { + return TRUNCATED; + } + } else if ( $box->type == 'pixi' ) { + // See ISO/IEC 23008-12:2017(E) 6.5.6.2 + if ( $box->content_size < 1 ) { + return INVALID; + } + if ( !( $data = read( $this->handle, 1 ) ) ) { + return TRUNCATED; + } + $num_channels = read_big_endian( $data, 1 ); + if ( $num_channels < 1 ) { + return INVALID; + } + if ( $box->content_size < 1 + $num_channels ) { + return INVALID; + } + if ( !( $data = read( $this->handle, 1 ) ) ) { + return TRUNCATED; + } + $bit_depth = read_big_endian( $data, 1 ); + if ( $bit_depth < 1 ) { + return INVALID; + } + for ( $i = 1; $i < $num_channels; ++$i ) { + if ( !( $data = read( $this->handle, 1 ) ) ) { + return TRUNCATED; + } + // Bit depth should be the same for all channels. + if ( read_big_endian( $data, 1 ) != $bit_depth ) { + return INVALID; + } + if ( $i > 32 ) { + return ABORTED; // Be reasonable. + } + } + if ( count( $this->features->chan_props ) <= MAX_FEATURES && + $box_index <= MAX_VALUE && $bit_depth <= MAX_VALUE && + $num_channels <= MAX_VALUE ) { + $chan_prop_count = count( $this->features->chan_props ); + $this->features->chan_props[$chan_prop_count] = new Chan_Prop(); + $this->features->chan_props[$chan_prop_count]->property_index = $box_index; + $this->features->chan_props[$chan_prop_count]->bit_depth = $bit_depth; + $this->features->chan_props[$chan_prop_count]->num_channels = $num_channels; + } else { + $this->data_was_skipped = true; + } + if ( !skip( $this->handle, $box->content_size - ( 1 + $num_channels ) ) ) { + return TRUNCATED; + } + } else if ( $box->type == 'av1C' ) { + // 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. + if ( $box->content_size < 3 ) { + return INVALID; + } + if ( !( $data = read( $this->handle, 3 ) ) ) { + return TRUNCATED; + } + $byte = read_big_endian( substr( $data, 2, 1 ), 1 ); + $high_bitdepth = ( $byte & 0x40 ) != 0; + $twelve_bit = ( $byte & 0x20 ) != 0; + $monochrome = ( $byte & 0x10 ) != 0; + if ( $twelve_bit && !$high_bitdepth ) { + return INVALID; + } + if ( count( $this->features->chan_props ) <= MAX_FEATURES && + $box_index <= MAX_VALUE ) { + $chan_prop_count = count( $this->features->chan_props ); + $this->features->chan_props[$chan_prop_count] = new Chan_Prop(); + $this->features->chan_props[$chan_prop_count]->property_index = $box_index; + $this->features->chan_props[$chan_prop_count]->bit_depth = + $high_bitdepth ? $twelve_bit ? 12 : 10 : 8; + $this->features->chan_props[$chan_prop_count]->num_channels = $monochrome ? 1 : 3; + } else { + $this->data_was_skipped = true; + } + if ( !skip( $this->handle, $box->content_size - 3 ) ) { + return TRUNCATED; + } + } else if ( $box->type == 'auxC' ) { + // See AV1 Image File Format (AVIF) 4 + // at https://aomediacodec.github.io/av1-avif/#auxiliary-images + $kAlphaStr = "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"; + $kAlphaStrLength = 44; // Includes terminating character. + if ( $box->content_size >= $kAlphaStrLength ) { + if ( !( $data = read( $this->handle, $kAlphaStrLength ) ) ) { + return TRUNCATED; + } + if ( substr( $data, 0, $kAlphaStrLength ) == $kAlphaStr ) { + // 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. + $this->features->has_alpha = true; + } + if ( !skip( $this->handle, $box->content_size - $kAlphaStrLength ) ) { + return TRUNCATED; + } + } else { + if ( !skip( $this->handle, $box->content_size ) ) { + return TRUNCATED; + } + } + } else { + if ( !skip( $this->handle, $box->content_size ) ) { + return TRUNCATED; + } + } + ++$box_index; + $num_remaining_bytes -= $box->size; + } while ( $num_remaining_bytes > 0 ); + return NOT_FOUND; + } + + /** + * Parses an "iprp" box. + * + * The "ipco" box contain the properties which are linked to items by the "ipma" box. + * + * @param stream $handle The resource the box will be parsed from. + * @param int $num_remaining_bytes The number of bytes that should be available from the resource. + * @return Status FOUND on success or an error on failure. + */ + private function parse_iprp( $num_remaining_bytes ) { + do { + $box = new Box(); + $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); + if ( $status != FOUND ) { + return $status; + } + + if ( $box->type == 'ipco' ) { + $status = $this->parse_ipco( $box->content_size ); + if ( $status != NOT_FOUND ) { + return $status; + } + } else if ( $box->type == 'ipma' ) { + // See ISO/IEC 23008-12:2017(E) 9.3.2 + $num_read_bytes = 4; + if ( $box->content_size < $num_read_bytes ) { + return INVALID; + } + if ( !( $data = read( $this->handle, $num_read_bytes ) ) ) { + return TRUNCATED; + } + $entry_count = read_big_endian( $data, 4 ); + $id_num_bytes = ( $box->version < 1 ) ? 2 : 4; + $index_num_bytes = ( $box->flags & 1 ) ? 2 : 1; + $essential_bit_mask = ( $box->flags & 1 ) ? 0x8000 : 0x80; + + for ( $entry = 0; $entry < $entry_count; ++$entry ) { + if ( $entry >= MAX_PROPS || + count( $this->features->props ) >= MAX_PROPS ) { + $this->data_was_skipped = true; + break; + } + $num_read_bytes += $id_num_bytes + 1; + if ( $box->content_size < $num_read_bytes ) { + return INVALID; + } + if ( !( $data = read( $this->handle, $id_num_bytes + 1 ) ) ) { + return TRUNCATED; + } + $item_id = read_big_endian( + substr( $data, 0, $id_num_bytes ), $id_num_bytes ); + $association_count = read_big_endian( + substr( $data, $id_num_bytes, 1 ), 1 ); + + for ( $property = 0; $property < $association_count; ++$property ) { + if ( $property >= MAX_PROPS || + count( $this->features->props ) >= MAX_PROPS ) { + $this->data_was_skipped = true; + break; + } + $num_read_bytes += $index_num_bytes; + if ( $box->content_size < $num_read_bytes ) { + return INVALID; + } + if ( !( $data = read( $this->handle, $index_num_bytes ) ) ) { + return TRUNCATED; + } + $value = read_big_endian( $data, $index_num_bytes ); + // $essential = ($value & $essential_bit_mask); // Unused. + $property_index = ( $value & ~$essential_bit_mask ); + if ( $property_index <= MAX_VALUE && $item_id <= MAX_VALUE ) { + $prop_count = count( $this->features->props ); + $this->features->props[$prop_count] = new Prop(); + $this->features->props[$prop_count]->property_index = $property_index; + $this->features->props[$prop_count]->item_id = $item_id; + } else { + $this->data_was_skipped = true; + } + } + if ( $property < $association_count ) { + break; // Do not read garbage. + } + } + + // If all features are available now, do not look further. + $status = $this->features->get_primary_item_features(); + if ( $status != NOT_FOUND ) { + return $status; + } + + // Mostly if 'data_was_skipped'. + if ( !skip( $this->handle, $box->content_size - $num_read_bytes ) ) { + return TRUNCATED; + } + } else { + if ( !skip( $this->handle, $box->content_size ) ) { + return TRUNCATED; + } + } + $num_remaining_bytes -= $box->size; + } while ( $num_remaining_bytes > 0 ); + return NOT_FOUND; + } + + /** + * Parses an "iref" box. + * + * 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. + * + * @param stream $handle The resource the box will be parsed from. + * @param int $num_remaining_bytes The number of bytes that should be available from the resource. + * @return Status FOUND on success or an error on failure. + */ + private function parse_iref( $num_remaining_bytes ) { + do { + $box = new Box(); + $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); + if ( $status != FOUND ) { + return $status; + } + + if ( $box->type == 'dimg' ) { + // See ISO/IEC 14496-12:2015(E) 8.11.12.2 + $num_bytes_per_id = ( $box->version == 0 ) ? 2 : 4; + $num_read_bytes = $num_bytes_per_id + 2; + if ( $box->content_size < $num_read_bytes ) { + return INVALID; + } + if ( !( $data = read( $this->handle, $num_read_bytes ) ) ) { + return TRUNCATED; + } + $from_item_id = read_big_endian( $data, $num_bytes_per_id ); + $reference_count = read_big_endian( substr( $data, $num_bytes_per_id, 2 ), 2 ); + + for ( $i = 0; $i < $reference_count; ++$i ) { + if ( $i >= MAX_TILES ) { + $this->data_was_skipped = true; + break; + } + $num_read_bytes += $num_bytes_per_id; + if ( $box->content_size < $num_read_bytes ) { + return INVALID; + } + if ( !( $data = read( $this->handle, $num_bytes_per_id ) ) ) { + return TRUNCATED; + } + $to_item_id = read_big_endian( $data, $num_bytes_per_id ); + $tile_count = count( $this->features->tiles ); + if ( $from_item_id <= MAX_VALUE && $to_item_id <= MAX_VALUE && + $tile_count < MAX_TILES ) { + $this->features->tiles[$tile_count] = new Tile(); + $this->features->tiles[$tile_count]->tile_item_id = $to_item_id; + $this->features->tiles[$tile_count]->parent_item_id = $from_item_id; + } else { + $this->data_was_skipped = true; + } + } + + // If all features are available now, do not look further. + $status = $this->features->get_primary_item_features(); + if ( $status != NOT_FOUND ) { + return $status; + } + + // Mostly if 'data_was_skipped'. + if ( !skip( $this->handle, $box->content_size - $num_read_bytes ) ) { + return TRUNCATED; + } + } else { + if ( !skip( $this->handle, $box->content_size ) ) { + return TRUNCATED; + } + } + $num_remaining_bytes -= $box->size; + } while ( $num_remaining_bytes > 0 ); + return NOT_FOUND; + } + + /** + * Parses a "meta" box. + * + * It looks for the primary item ID in the "pitm" box and recurses into other boxes + * to find its features. + * + * @param stream $handle The resource the box will be parsed from. + * @param int $num_remaining_bytes The number of bytes that should be available from the resource. + * @return Status FOUND on success or an error on failure. + */ + private function parse_meta( $num_remaining_bytes ) { + do { + $box = new Box(); + $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); + if ( $status != FOUND ) { + return $status; + } + + if ( $box->type == 'pitm' ) { + // See ISO/IEC 14496-12:2015(E) 8.11.4.2 + $num_bytes_per_id = ( $box->version == 0 ) ? 2 : 4; + if ( $num_bytes_per_id > $num_remaining_bytes ) { + return INVALID; + } + if ( !( $data = read( $this->handle, $num_bytes_per_id ) ) ) { + return TRUNCATED; + } + $primary_item_id = read_big_endian( $data, $num_bytes_per_id ); + if ( $primary_item_id > MAX_VALUE ) { + return ABORTED; + } + $this->features->has_primary_item = true; + $this->features->primary_item_id = $primary_item_id; + if ( !skip( $this->handle, $box->content_size - $num_bytes_per_id ) ) { + return TRUNCATED; + } + } else if ( $box->type == 'iprp' ) { + $status = $this->parse_iprp( $box->content_size ); + if ( $status != NOT_FOUND ) { + return $status; + } + } else if ( $box->type == 'iref' ) { + $status = $this->parse_iref( $box->content_size ); + if ( $status != NOT_FOUND ) { + return $status; + } + } else { + if ( !skip( $this->handle, $box->content_size ) ) { + return TRUNCATED; + } + } + $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". + return INVALID; + } + + /** + * Parses a file stream. + * + * The file type is checked through the "ftyp" box. + * + * @return bool True if the input stream is an AVIF bitstream or false. + */ + public function parse_ftyp() { + $box = new Box(); + $status = $box->parse( $this->handle, $this->num_parsed_boxes ); + if ( $status != FOUND ) { + return false; + } + + if ( $box->type != 'ftyp' ) { + return false; + } + // Iterate over brands. See ISO/IEC 14496-12:2012(E) 4.3.1 + if ( $box->content_size < 8 ) { + return false; + } + for ( $i = 0; $i + 4 <= $box->content_size; $i += 4 ) { + if ( !( $data = read( $this->handle, 4 ) ) ) { + return false; + } + if ( $i == 4 ) { + continue; // Skip minor_version. + } + if ( substr( $data, 0, 4 ) == 'avif' || substr( $data, 0, 4 ) == 'avis' ) { + return skip( $this->handle, $box->content_size - ( $i + 4 ) ); + } + if ( $i > 32 * 4 ) { + return false; // Be reasonable. + } + + } + return false; // No AVIF brand no good. + } + + /** + * Parses a file stream. + * + * Features are extracted from the "meta" box. + * + * @return bool True if the main features of the primary item were parsed or false. + */ + public function parse_file() { + $box = new Box(); + while ( $box->parse( $this->handle, $this->num_parsed_boxes ) == FOUND ) { + if ( $box->type === 'meta' ) { + if ( $this->parse_meta( $box->content_size ) != FOUND ) { + return false; + } + return true; + } + if ( !skip( $this->handle, $box->content_size ) ) { + return false; + } + } + return false; // No "meta" no good. + } +} diff --git a/src/wp-includes/class-wp-image-editor-gd.php b/src/wp-includes/class-wp-image-editor-gd.php index 03525ce9431e3..6bd9d9676c4a2 100644 --- a/src/wp-includes/class-wp-image-editor-gd.php +++ b/src/wp-includes/class-wp-image-editor-gd.php @@ -71,7 +71,9 @@ public static function supports_mime_type( $mime_type ) { return ( $image_types & IMG_GIF ) != 0; case 'image/webp': return ( $image_types & IMG_WEBP ) != 0; - } + case 'image/avif': + return ( $image_types & IMG_AVIF ) != 0; + } return false; } @@ -111,6 +113,16 @@ function_exists( 'imagecreatefromwebp' ) && $this->image = @imagecreatefromstring( $file_contents ); } + // AVIF may not work with imagecreatefromstring(). + if ( + function_exists( 'imagecreatefromavif' ) && + ( 'image/avif' === wp_get_image_mime( $this->file ) ) + ) { + $this->image = @imagecreatefromavif( $this->file ); + } else { + $this->image = @imagecreatefromstring( $file_contents ); + } + if ( ! is_gd_image( $this->image ) ) { return new WP_Error( 'invalid_image', __( 'File is not an image.' ), $this->file ); } @@ -497,6 +509,10 @@ protected function _save( $image, $filename = null, $mime_type = null ) { if ( ! function_exists( 'imagewebp' ) || ! $this->make_image( $filename, 'imagewebp', array( $image, $filename, $this->get_quality() ) ) ) { return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) ); } + } elseif ( 'image/avif' == $mime_type ) { + if ( ! function_exists( 'imageavif' ) || ! $this->make_image( $filename, 'imageavif', array( $image, $filename, $this->get_quality() ) ) ) { + return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) ); + } } else { return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) ); } @@ -546,7 +562,11 @@ public function stream( $mime_type = null ) { header( 'Content-Type: image/webp' ); return imagewebp( $this->image, null, $this->get_quality() ); } - // Fall back to the default if webp isn't supported. + case 'image/avif': + if ( function_exists( 'imageavif' ) ) { + header( 'Content-Type: image/avif' ); + return imageavif( $this->image, null, $this->get_quality() ); + } default: header( 'Content-Type: image/jpeg' ); return imagejpeg( $this->image, null, $this->get_quality() ); diff --git a/src/wp-includes/class-wp-image-editor-imagick.php b/src/wp-includes/class-wp-image-editor-imagick.php index 69ab7b176c41b..08ddb3e3a880f 100644 --- a/src/wp-includes/class-wp-image-editor-imagick.php +++ b/src/wp-includes/class-wp-image-editor-imagick.php @@ -213,6 +213,15 @@ public function set_quality( $quality = null ) { $this->image->setImageCompressionQuality( $quality ); } break; + case 'image/avif': + $avif_info = wp_get_avif_info( $this->file ); + if ( 'lossless' === $avif_info['type'] ) { + // Use quality 100 to trigger AVIF lossless. + $this->image->setImageCompressionQuality( 100 ); + } else { + $this->image->setImageCompressionQuality( $quality ); + } + break; default: $this->image->setImageCompressionQuality( $quality ); } diff --git a/src/wp-includes/class-wp-image-editor.php b/src/wp-includes/class-wp-image-editor.php index fed0fc16e8ef1..788e6b1568005 100644 --- a/src/wp-includes/class-wp-image-editor.php +++ b/src/wp-includes/class-wp-image-editor.php @@ -311,6 +311,7 @@ protected function get_default_quality( $mime_type ) { $quality = 86; break; case 'image/jpeg': + case 'image/avif': default: $quality = $this->default_quality; } diff --git a/src/wp-includes/class-wp-theme.php b/src/wp-includes/class-wp-theme.php index 7cde57f4f9d5a..7fbd60b65c2ef 100644 --- a/src/wp-includes/class-wp-theme.php +++ b/src/wp-includes/class-wp-theme.php @@ -1204,7 +1204,7 @@ public function get_screenshot( $uri = 'uri' ) { return false; } - foreach ( array( 'png', 'gif', 'jpg', 'jpeg', 'webp' ) as $ext ) { + foreach ( array( 'png', 'gif', 'jpg', 'jpeg', 'webp', 'avif' ) as $ext ) { if ( file_exists( $this->get_stylesheet_directory() . "/screenshot.$ext" ) ) { $this->cache_add( 'screenshot', 'screenshot.' . $ext ); if ( 'relative' === $uri ) { diff --git a/src/wp-includes/customize/class-wp-customize-media-control.php b/src/wp-includes/customize/class-wp-customize-media-control.php index 22bf75dad52ac..4b5e845135a0e 100644 --- a/src/wp-includes/customize/class-wp-customize-media-control.php +++ b/src/wp-includes/customize/class-wp-customize-media-control.php @@ -91,7 +91,7 @@ public function to_json() { // Fake an attachment model - needs all fields used by template. // Note that the default value must be a URL, NOT an attachment ID. $ext = substr( $this->setting->default, -3 ); - $type = in_array( $ext, array( 'jpg', 'png', 'gif', 'bmp', 'webp' ), true ) ? 'image' : 'document'; + $type = in_array( $ext, array( 'jpg', 'png', 'gif', 'bmp', 'webp', 'avif' ), true ) ? 'image' : 'document'; $default_attachment = array( 'id' => 1, diff --git a/src/wp-includes/deprecated.php b/src/wp-includes/deprecated.php index ca760ff7868a6..6916013ee469b 100644 --- a/src/wp-includes/deprecated.php +++ b/src/wp-includes/deprecated.php @@ -3326,7 +3326,9 @@ function gd_edit_image_support($mime_type) { return (imagetypes() & IMG_GIF) != 0; case 'image/webp': return (imagetypes() & IMG_WEBP) != 0; - } + case 'image/avif': + return (imagetypes() & IMG_AVIF) != 0; + } } else { switch( $mime_type ) { case 'image/jpeg': @@ -3337,6 +3339,8 @@ function gd_edit_image_support($mime_type) { return function_exists('imagecreatefromgif'); case 'image/webp': return function_exists('imagecreatefromwebp'); + case 'image/avif': + return function_exists('imagecreatefromavif'); } } return false; diff --git a/src/wp-includes/formatting.php b/src/wp-includes/formatting.php index 9ea2b9f8fa78e..59ac59eb32904 100644 --- a/src/wp-includes/formatting.php +++ b/src/wp-includes/formatting.php @@ -3440,7 +3440,7 @@ function translate_smiley( $matches ) { $matches = array(); $ext = preg_match( '/\.([^.]+)$/', $img, $matches ) ? strtolower( $matches[1] ) : false; - $image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'webp' ); + $image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'webp', 'avif' ); // Don't convert smilies that aren't images - they're probably emoji. if ( ! in_array( $ext, $image_exts, true ) ) { diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 10ad8e8b5e589..cccb71eb6c58c 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -3104,6 +3104,7 @@ function wp_check_filetype_and_ext( $file, $filename, $mimes = null ) { 'image/bmp' => 'bmp', 'image/tiff' => 'tif', 'image/webp' => 'webp', + 'image/avif' => 'avif', ) ); @@ -3263,6 +3264,7 @@ function wp_check_filetype_and_ext( $file, $filename, $mimes = null ) { * * @since 4.7.1 * @since 5.8.0 Added support for WebP images. + * @since 6.4.0 Added support for AVIF images. * * @param string $file Full path to the file. * @return string|false The actual mime type or false if the type cannot be determined. @@ -3318,6 +3320,21 @@ function wp_get_image_mime( $file ) { ) { $mime = 'image/webp'; } + + /** + * Add AVIF fallback detection when image library doesn't support AVIF. + * + * Note: detection values come from libavif. + * + */ + if ( + // ftyp. + ( str_starts_with( $magic, '66747970' ) ) && + // avif. + ( 8 === strpos( $magic, '61766966' ) ) + ) { + $mime = 'image/avif'; + } } catch ( Exception $e ) { $mime = false; } @@ -3357,6 +3374,7 @@ function wp_get_mime_types() { 'bmp' => 'image/bmp', 'tiff|tif' => 'image/tiff', 'webp' => 'image/webp', + 'avif' => 'image/avif', 'ico' => 'image/x-icon', 'heic' => 'image/heic', // Video formats. @@ -3478,7 +3496,7 @@ function wp_get_ext_types() { return apply_filters( 'ext2type', array( - 'image' => array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'bmp', 'tif', 'tiff', 'ico', 'heic', 'webp' ), + 'image' => array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'bmp', 'tif', 'tiff', 'ico', 'heic', 'webp', 'avif' ), 'audio' => array( 'aac', 'ac3', 'aif', 'aiff', 'flac', 'm3a', 'm4a', 'm4b', 'mka', 'mp1', 'mp2', 'mp3', 'ogg', 'oga', 'ram', 'wav', 'wma' ), 'video' => array( '3g2', '3gp', '3gpp', 'asf', 'avi', 'divx', 'dv', 'flv', 'm4v', 'mkv', 'mov', 'mp4', 'mpeg', 'mpg', 'mpv', 'ogm', 'ogv', 'qt', 'rm', 'vob', 'wmv' ), 'document' => array( 'doc', 'docx', 'docm', 'dotm', 'odt', 'pages', 'pdf', 'xps', 'oxps', 'rtf', 'wp', 'wpd', 'psd', 'xcf' ), diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index b74933881f909..6f95e653c2599 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -3969,6 +3969,7 @@ function _wp_image_editor_choose( $args = array() ) { require_once ABSPATH . WPINC . '/class-wp-image-editor.php'; require_once ABSPATH . WPINC . '/class-wp-image-editor-gd.php'; require_once ABSPATH . WPINC . '/class-wp-image-editor-imagick.php'; + require_once ABSPATH . WPINC . '/class-avif-info.php'; /** * Filters the list of image editing library classes. * @@ -4070,6 +4071,12 @@ function wp_plupload_default_settings() { $defaults['webp_upload_error'] = true; } + + // Check if AVIF images can be edited. + if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/avif' ) ) ) { + $defaults['avif_upload_error'] = true; + } + /** * Filters the Plupload default settings. * @@ -5335,6 +5342,7 @@ function wp_show_heic_upload_error( $plupload_settings ) { * * @since 5.7.0 * @since 5.8.0 Added support for WebP images. + * @since 6.4.0 Added support for AVIF images. * * @param string $filename The file path. * @param array $image_info Optional. Extended image information (passed by reference). @@ -5396,10 +5404,96 @@ function wp_getimagesize( $filename, array &$image_info = null ) { } } + // For PHP versions that don't support AVIF images, + // extract the image size info from the file headers. + if ( 'image/avif' === wp_get_image_mime( $filename ) ) { + $avif_info = wp_get_avif_info( $filename ); + $width = $avif_info['width']; + $height = $avif_info['height']; + + // Mimic the native return format. + if ( $width && $height ) { + return array( + $width, + $height, + IMAGETYPE_AVIF, + sprintf( + 'width="%d" height="%d"', + $width, + $height + ), + 'mime' => 'image/avif', + ); + } + } + + // The image could not be parsed. return false; } +/** + * Extracts meta information about an AVIF file: width, height, and type. + * + * @since 6.4.0 + * + * @param string $filename Path to an AVIF file. + * @return array { + * An array of AVIF image information. + * + * @type int|false $width Image width on success, false on failure. + * @type int|false $height Image height on success, false on failure. + * @type string|false $type The AVIF type: one of 'lossy' or 'lossless'. False on failure. + * } + */ +function wp_get_avif_info( $filename ) { + $width = false; + $height = false; + $type = false; + + if ( 'image/avif' !== wp_get_image_mime( $filename ) ) { + return compact( 'width', 'height', 'type' ); + } + + if ( function_exists( 'avif_get_info' ) ) { + + $avif_info = avif_get_info( $filename ); + + if ( ! $avif_info ) { + return compact( 'width', 'height', 'type' ); + } + + $width = $avif_info['width']; + $height = $avif_info['height']; + $type = $avif_info['type']; + + return compact( 'width', 'height', 'type' ); + } else { + // Fall back to directly parsing the file headers. + require_once ABSPATH . WPINC . '/class-avif-info.php'; + $features = array( + 'width' => false, + 'height' => false, + 'bit_depth' => false, + 'num_channels' => false + ); + + $handle = fopen( $filename, 'rb' ); + if ( $handle ) { + $parser = new Avifinfo\Parser( $handle ); + $success = $parser->parse_ftyp() && $parser->parse_file(); + fclose( $handle ); + if ( $success ) { + $features = $parser->features->primary_item_features; + } + } + + return $features; + + } + +} + /** * Extracts meta information about a WebP file: width, height, and type. * diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index fabd68edb23b4..e4c5e2b739136 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -6652,7 +6652,7 @@ function wp_attachment_is( $type, $post = null ) { switch ( $type ) { case 'image': - $image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'webp' ); + $image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'webp', 'avif' ); return in_array( $ext, $image_exts, true ); case 'audio': diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php index 98c0a1bb642bd..44102c2d236c7 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php @@ -446,7 +446,7 @@ public function edit_media_item( $request ) { ); } - $supported_types = array( 'image/jpeg', 'image/png', 'image/gif', 'image/webp' ); + $supported_types = array( 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif' ); $mime_type = get_post_mime_type( $attachment_id ); if ( ! in_array( $mime_type, $supported_types, true ) ) { return new WP_Error( From 5b622d17ca6309a5d2dc1f846439ed6f2e8792b4 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Tue, 13 Jun 2023 16:05:25 -0600 Subject: [PATCH 02/79] whitespace --- src/wp-includes/media.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 6f95e653c2599..c432f25903185 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -4071,7 +4071,6 @@ function wp_plupload_default_settings() { $defaults['webp_upload_error'] = true; } - // Check if AVIF images can be edited. if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/avif' ) ) ) { $defaults['avif_upload_error'] = true; From bb9642faafe3f98729d376d622bd668b4ae39dcd Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Wed, 14 Jun 2023 09:48:31 -0600 Subject: [PATCH 03/79] Add AVIF constants --- src/wp-includes/compat.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/wp-includes/compat.php b/src/wp-includes/compat.php index f9df28eac9eb4..843e0b2b8a0c7 100644 --- a/src/wp-includes/compat.php +++ b/src/wp-includes/compat.php @@ -501,3 +501,13 @@ function str_ends_with( $haystack, $needle ) { if ( ! defined( 'IMG_WEBP' ) ) { define( 'IMG_WEBP', IMAGETYPE_WEBP ); } + +// IMAGETYPE_AVIF constant is only defined in PHP 8.x or later. +if ( ! defined( 'IMAGETYPE_AVIF' ) ) { + define( 'IMAGETYPE_AVIF', 19 ); +} + +// IMG_AVIF constant is only defined in PHP 8.x or later. +if ( ! defined( 'IMG_AVIF' ) ) { + define( 'IMG_AVIF', IMAGETYPE_AVIF ); +} From 3bd1ae834431c055ccc6b6351df11c0a81ee5aff Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 15 Jun 2023 13:16:12 -0600 Subject: [PATCH 04/79] update magic numbers for avif detection --- src/wp-includes/functions.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index cccb71eb6c58c..8c322d323d72d 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -3328,10 +3328,8 @@ function wp_get_image_mime( $file ) { * */ if ( - // ftyp. - ( str_starts_with( $magic, '66747970' ) ) && // avif. - ( 8 === strpos( $magic, '61766966' ) ) + ( str_starts_with( $magic, '0000002066' ) ) ) { $mime = 'image/avif'; } From bc5fc5b59625cbb1aa2ff23469bf513bf4fac903 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 15 Jun 2023 13:40:14 -0600 Subject: [PATCH 05/79] Include AVIF in the displayable image types --- src/wp-admin/includes/image.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-admin/includes/image.php b/src/wp-admin/includes/image.php index f937bdc2ff53d..005d81b5eb096 100644 --- a/src/wp-admin/includes/image.php +++ b/src/wp-admin/includes/image.php @@ -970,7 +970,7 @@ function file_is_valid_image( $path ) { * @return bool True if suitable, false if not suitable. */ function file_is_displayable_image( $path ) { - $displayable_image_types = array( IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG, IMAGETYPE_BMP, IMAGETYPE_ICO, IMAGETYPE_WEBP ); + $displayable_image_types = array( IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG, IMAGETYPE_BMP, IMAGETYPE_ICO, IMAGETYPE_WEBP, IMAGETYPE_AVIF ); $info = wp_getimagesize( $path ); if ( empty( $info ) ) { From 3e7228d5568268ef632050979189b9a7474e03ae Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 15 Jun 2023 13:48:45 -0600 Subject: [PATCH 06/79] phpcbf --- src/wp-admin/includes/image-edit.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-admin/includes/image-edit.php b/src/wp-admin/includes/image-edit.php index 7f39d7d62a4c1..76aaa11eae666 100644 --- a/src/wp-admin/includes/image-edit.php +++ b/src/wp-admin/includes/image-edit.php @@ -392,7 +392,7 @@ function wp_stream_image( $image, $mime_type, $attachment_id ) { header( 'Content-Type: image/avif' ); return imageavif( $image, null, 90 ); } - return false; + return false; default: return false; } From 8f64fe8789e1dc0757296b4e606cdb2134129d48 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 16 Jun 2023 07:11:05 -0600 Subject: [PATCH 07/79] Apply phpcbf fixes to avif helper --- src/wp-includes/class-avif-info.php | 1393 ++++++++++++++------------- 1 file changed, 700 insertions(+), 693 deletions(-) diff --git a/src/wp-includes/class-avif-info.php b/src/wp-includes/class-avif-info.php index 84a2e9333de08..b1a26e35967f3 100644 --- a/src/wp-includes/class-avif-info.php +++ b/src/wp-includes/class-avif-info.php @@ -34,18 +34,18 @@ * @return int Value. */ function read_big_endian( $input, $num_bytes ) { - if ( $num_bytes == 1 ) { - return unpack( 'C', $input ) [1]; - } else if ( $num_bytes == 2 ) { - return unpack( 'n', $input ) [1]; - } else if ( $num_bytes == 3 ) { - $bytes = unpack( 'C3', $input ); - return ( $bytes[1] << 16 ) | ( $bytes[2] << 8 ) | $bytes[3]; - } else { // $num_bytes is 4 - // This might fail to read unsigned values >= 2^31 on 32-bit systems. - // See https://www.php.net/manual/en/function.unpack.php#106041 - return unpack( 'N', $input ) [1]; - } + if ( $num_bytes == 1 ) { + return unpack( 'C', $input ) [1]; + } elseif ( $num_bytes == 2 ) { + return unpack( 'n', $input ) [1]; + } elseif ( $num_bytes == 3 ) { + $bytes = unpack( 'C3', $input ); + return ( $bytes[1] << 16 ) | ( $bytes[2] << 8 ) | $bytes[3]; + } else { // $num_bytes is 4 + // This might fail to read unsigned values >= 2^31 on 32-bit systems. + // See https://www.php.net/manual/en/function.unpack.php#106041 + return unpack( 'N', $input ) [1]; + } } /** @@ -56,8 +56,8 @@ function read_big_endian( $input, $num_bytes ) { * @return binary string|false The raw bytes or false on failure. */ function read( $handle, $num_bytes ) { - $data = fread( $handle, $num_bytes ); - return ( $data !== false && strlen( $data ) >= $num_bytes ) ? $data : false; + $data = fread( $handle, $num_bytes ); + return ( $data !== false && strlen( $data ) >= $num_bytes ) ? $data : false; } /** @@ -69,707 +69,714 @@ function read( $handle, $num_bytes ) { */ // Skips 'num_bytes' from the 'stream'. 'num_bytes' can be zero. function skip( $handle, $num_bytes ) { - return ( fseek( $handle, $num_bytes, SEEK_CUR ) == 0 ); + return ( fseek( $handle, $num_bytes, SEEK_CUR ) == 0 ); } //------------------------------------------------------------------------------ // Features are parsed into temporary property associations. -class Tile { // Tile item id <-> parent item id associations. - public $tile_item_id; - public $parent_item_id; +class Tile { + // Tile item id <-> parent item id associations. + public $tile_item_id; + public $parent_item_id; } -class Prop { // Property index <-> item id associations. - public $property_index; - public $item_id; +class Prop { + // Property index <-> item id associations. + public $property_index; + public $item_id; } -class Dim_Prop { // Property <-> features associations. - public $property_index; - public $width; - public $height; +class Dim_Prop { + // Property <-> features associations. + public $property_index; + public $width; + public $height; } -class Chan_Prop { // Property <-> features associations. - public $property_index; - public $bit_depth; - public $num_channels; +class Chan_Prop { + // Property <-> features associations. + public $property_index; + public $bit_depth; + public $num_channels; } class Features { - public $has_primary_item = false; // True if "pitm" was parsed. - public $has_alpha = false; // True if an alpha "auxC" was parsed. - public $primary_item_id; - public $primary_item_features = array( // Deduced from the data below. - 'width' => UNDEFINED, // In number of pixels. - 'height' => UNDEFINED, // Ignores mirror and rotation. - 'bit_depth' => UNDEFINED, // Likely 8, 10 or 12 bits per channel per pixel. - 'num_channels' => UNDEFINED // Likely 1, 2, 3 or 4 channels: - // (1 monochrome or 3 colors) + (0 or 1 alpha) - ); - - public $tiles = array(); // Tile[] - public $props = array(); // Prop[] - public $dim_props = array(); // Dim_Prop[] - public $chan_props = array(); // Chan_Prop[] - - /** - * Binds the width, height, bit depth and number of channels from stored internal features. - * - * @param int $target_item_id Id of the item whose features will be bound. - * @param int $tile_depth Maximum recursion to search within tile-parent relations. - * @return Status FOUND on success or NOT_FOUND on failure. - */ - private function get_item_features( $target_item_id, $tile_depth ) { - foreach ( $this->props as $prop ) { - if ( $prop->item_id != $target_item_id ) { - continue; - } - - // Retrieve the width and height of the primary item if not already done. - if ( $target_item_id == $this->primary_item_id && - ( $this->primary_item_features['width'] == UNDEFINED || - $this->primary_item_features['height'] == UNDEFINED ) ) { - foreach ( $this->dim_props as $dim_prop ) { - if ( $dim_prop->property_index != $prop->property_index ) { - continue; - } - $this->primary_item_features['width'] = $dim_prop->width; - $this->primary_item_features['height'] = $dim_prop->height; - if ( $this->primary_item_features['bit_depth'] != UNDEFINED && - $this->primary_item_features['num_channels'] != UNDEFINED ) { - return FOUND; - } - break; - } - } - // Retrieve the bit depth and number of channels of the target item if not - // already done. - if ( $this->primary_item_features['bit_depth'] == UNDEFINED || - $this->primary_item_features['num_channels'] == UNDEFINED ) { - foreach ( $this->chan_props as $chan_prop ) { - if ( $chan_prop->property_index != $prop->property_index ) { - continue; - } - $this->primary_item_features['bit_depth'] = $chan_prop->bit_depth; - $this->primary_item_features['num_channels'] = $chan_prop->num_channels; - if ( $this->primary_item_features['width'] != UNDEFINED && - $this->primary_item_features['height'] != UNDEFINED ) { - return FOUND; - } - break; - } - } - } - - // Check for the bit_depth and num_channels in a tile if not yet found. - if ( $tile_depth < 3 ) { - foreach ( $this->tiles as $tile ) { - if ( $tile->parent_item_id != $target_item_id ) { - continue; - } - $status = get_item_features( $tile->tile_item_id, $tile_depth + 1 ); - if ( $status != NOT_FOUND ) { - return $status; - } - } - } - return NOT_FOUND; - } - - /** - * Finds the width, height, bit depth and number of channels of the primary item. - * - * @return Status FOUND on success or NOT_FOUND on failure. - */ - public function get_primary_item_features() { - // Nothing to do without the primary item ID. - if ( !$this->has_primary_item ) { - return NOT_FOUND; - } - // Early exit. - if ( empty( $this->dim_props ) || empty( $this->chan_props ) ) { - return NOT_FOUND; - } - $status = $this->get_item_features( $this->primary_item_id, /*tile_depth=*/ 0 ); - if ( $status != FOUND ) { - return $status; - } - - // "auxC" is parsed before the "ipma" properties so it is known now, if any. - if ( $this->has_alpha ) { - ++$this->primary_item_features['num_channels']; - } - return FOUND; - } + public $has_primary_item = false; // True if "pitm" was parsed. + public $has_alpha = false; // True if an alpha "auxC" was parsed. + public $primary_item_id; + public $primary_item_features = array( // Deduced from the data below. + 'width' => UNDEFINED, // In number of pixels. + 'height' => UNDEFINED, // Ignores mirror and rotation. + 'bit_depth' => UNDEFINED, // Likely 8, 10 or 12 bits per channel per pixel. + 'num_channels' => UNDEFINED, // Likely 1, 2, 3 or 4 channels: + // (1 monochrome or 3 colors) + (0 or 1 alpha) + ); + + public $tiles = array(); // Tile[] + public $props = array(); // Prop[] + public $dim_props = array(); // Dim_Prop[] + public $chan_props = array(); // Chan_Prop[] + + /** + * Binds the width, height, bit depth and number of channels from stored internal features. + * + * @param int $target_item_id Id of the item whose features will be bound. + * @param int $tile_depth Maximum recursion to search within tile-parent relations. + * @return Status FOUND on success or NOT_FOUND on failure. + */ + private function get_item_features( $target_item_id, $tile_depth ) { + foreach ( $this->props as $prop ) { + if ( $prop->item_id != $target_item_id ) { + continue; + } + + // Retrieve the width and height of the primary item if not already done. + if ( $target_item_id == $this->primary_item_id && + ( $this->primary_item_features['width'] == UNDEFINED || + $this->primary_item_features['height'] == UNDEFINED ) ) { + foreach ( $this->dim_props as $dim_prop ) { + if ( $dim_prop->property_index != $prop->property_index ) { + continue; + } + $this->primary_item_features['width'] = $dim_prop->width; + $this->primary_item_features['height'] = $dim_prop->height; + if ( $this->primary_item_features['bit_depth'] != UNDEFINED && + $this->primary_item_features['num_channels'] != UNDEFINED ) { + return FOUND; + } + break; + } + } + // Retrieve the bit depth and number of channels of the target item if not + // already done. + if ( $this->primary_item_features['bit_depth'] == UNDEFINED || + $this->primary_item_features['num_channels'] == UNDEFINED ) { + foreach ( $this->chan_props as $chan_prop ) { + if ( $chan_prop->property_index != $prop->property_index ) { + continue; + } + $this->primary_item_features['bit_depth'] = $chan_prop->bit_depth; + $this->primary_item_features['num_channels'] = $chan_prop->num_channels; + if ( $this->primary_item_features['width'] != UNDEFINED && + $this->primary_item_features['height'] != UNDEFINED ) { + return FOUND; + } + break; + } + } + } + + // Check for the bit_depth and num_channels in a tile if not yet found. + if ( $tile_depth < 3 ) { + foreach ( $this->tiles as $tile ) { + if ( $tile->parent_item_id != $target_item_id ) { + continue; + } + $status = get_item_features( $tile->tile_item_id, $tile_depth + 1 ); + if ( $status != NOT_FOUND ) { + return $status; + } + } + } + return NOT_FOUND; + } + + /** + * Finds the width, height, bit depth and number of channels of the primary item. + * + * @return Status FOUND on success or NOT_FOUND on failure. + */ + public function get_primary_item_features() { + // Nothing to do without the primary item ID. + if ( ! $this->has_primary_item ) { + return NOT_FOUND; + } + // Early exit. + if ( empty( $this->dim_props ) || empty( $this->chan_props ) ) { + return NOT_FOUND; + } + $status = $this->get_item_features( $this->primary_item_id, /*tile_depth=*/ 0 ); + if ( $status != FOUND ) { + return $status; + } + + // "auxC" is parsed before the "ipma" properties so it is known now, if any. + if ( $this->has_alpha ) { + ++$this->primary_item_features['num_channels']; + } + return FOUND; + } } //------------------------------------------------------------------------------ class Box { - public $size; // In bytes. - public $type; // Four characters. - public $version; // 0 or actual version if this is a full box. - public $flags; // 0 or actual value if this is a full box. - public $content_size; // 'size' minus the header size. - - /** - * Reads the box header. - * - * @param stream $handle The resource the header will be parsed from. - * @param int $num_parsed_boxes The total number of parsed boxes. Prevents timeouts. - * @param int $num_remaining_bytes The number of bytes that should be available from the resource. - * @return Status FOUND on success or an error on failure. - */ - public function parse( $handle, &$num_parsed_boxes, $num_remaining_bytes = MAX_SIZE ) { - // See ISO/IEC 14496-12:2012(E) 4.2 - $header_size = 8; // box 32b size + 32b type (at least) - if ( $header_size > $num_remaining_bytes ) { - return INVALID; - } - if ( !( $data = read( $handle, 8 ) ) ) { - return TRUNCATED; - } - $this->size = read_big_endian( $data, 4 ); - $this->type = substr( $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 ( $this->size == 1 ) { - $header_size += 8; - if ( $header_size > $num_remaining_bytes ) { - return INVALID; - } - if ( !( $data = read( $handle, 8 ) ) ) { - return TRUNCATED; - } - // Stop the parsing if any box has a size greater than 4GB. - if ( read_big_endian( $data, 4 ) != 0 ) { - return ABORTED; - } - // Read the 32 least-significant bits. - $this->size = read_big_endian( substr( $data, 4, 4 ), 4 ); - } else if ( $this->size == 0 ) { - $this->size = $num_remaining_bytes; - } - if ( $this->size < $header_size ) { - return INVALID; - } - if ( $this->size > $num_remaining_bytes ) { - return INVALID; - } - - $has_fullbox_header = $this->type == 'meta' || $this->type == 'pitm' || - $this->type == 'ipma' || $this->type == 'ispe' || - $this->type == 'pixi' || $this->type == 'iref' || - $this->type == 'auxC'; - if ( $has_fullbox_header ) { - $header_size += 4; - } - if ( $this->size < $header_size ) { - return INVALID; - } - $this->content_size = $this->size - $header_size; - // Avoid timeouts. The maximum number of parsed boxes is arbitrary. - ++$num_parsed_boxes; - if ( $num_parsed_boxes >= MAX_NUM_BOXES ) { - return ABORTED; - } - - $this->version = 0; - $this->flags = 0; - if ( $has_fullbox_header ) { - if ( !( $data = read( $handle, 4 ) ) ) { - return TRUNCATED; - } - $this->version = read_big_endian( $data, 1 ); - $this->flags = read_big_endian( substr( $data, 1, 3 ), 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). - $is_parsable = ( $this->type == 'meta' && $this->version <= 0 ) || - ( $this->type == 'pitm' && $this->version <= 1 ) || - ( $this->type == 'ipma' && $this->version <= 1 ) || - ( $this->type == 'ispe' && $this->version <= 0 ) || - ( $this->type == 'pixi' && $this->version <= 0 ) || - ( $this->type == 'iref' && $this->version <= 1 ) || - ( $this->type == 'auxC' && $this->version <= 0 ); - // Instead of considering this file as invalid, skip unparsable boxes. - if ( !$is_parsable ) { - $this->type = 'unknownversion'; - } - } - // print_r( $this ); // Uncomment to print all boxes. - return FOUND; - } + public $size; // In bytes. + public $type; // Four characters. + public $version; // 0 or actual version if this is a full box. + public $flags; // 0 or actual value if this is a full box. + public $content_size; // 'size' minus the header size. + + /** + * Reads the box header. + * + * @param stream $handle The resource the header will be parsed from. + * @param int $num_parsed_boxes The total number of parsed boxes. Prevents timeouts. + * @param int $num_remaining_bytes The number of bytes that should be available from the resource. + * @return Status FOUND on success or an error on failure. + */ + public function parse( $handle, &$num_parsed_boxes, $num_remaining_bytes = MAX_SIZE ) { + // See ISO/IEC 14496-12:2012(E) 4.2 + $header_size = 8; // box 32b size + 32b type (at least) + if ( $header_size > $num_remaining_bytes ) { + return INVALID; + } + if ( ! ( $data = read( $handle, 8 ) ) ) { + return TRUNCATED; + } + $this->size = read_big_endian( $data, 4 ); + $this->type = substr( $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 ( $this->size == 1 ) { + $header_size += 8; + if ( $header_size > $num_remaining_bytes ) { + return INVALID; + } + if ( ! ( $data = read( $handle, 8 ) ) ) { + return TRUNCATED; + } + // Stop the parsing if any box has a size greater than 4GB. + if ( read_big_endian( $data, 4 ) != 0 ) { + return ABORTED; + } + // Read the 32 least-significant bits. + $this->size = read_big_endian( substr( $data, 4, 4 ), 4 ); + } elseif ( $this->size == 0 ) { + $this->size = $num_remaining_bytes; + } + if ( $this->size < $header_size ) { + return INVALID; + } + if ( $this->size > $num_remaining_bytes ) { + return INVALID; + } + + $has_fullbox_header = $this->type == 'meta' || $this->type == 'pitm' || + $this->type == 'ipma' || $this->type == 'ispe' || + $this->type == 'pixi' || $this->type == 'iref' || + $this->type == 'auxC'; + if ( $has_fullbox_header ) { + $header_size += 4; + } + if ( $this->size < $header_size ) { + return INVALID; + } + $this->content_size = $this->size - $header_size; + // Avoid timeouts. The maximum number of parsed boxes is arbitrary. + ++$num_parsed_boxes; + if ( $num_parsed_boxes >= MAX_NUM_BOXES ) { + return ABORTED; + } + + $this->version = 0; + $this->flags = 0; + if ( $has_fullbox_header ) { + if ( ! ( $data = read( $handle, 4 ) ) ) { + return TRUNCATED; + } + $this->version = read_big_endian( $data, 1 ); + $this->flags = read_big_endian( substr( $data, 1, 3 ), 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). + $is_parsable = ( $this->type == 'meta' && $this->version <= 0 ) || + ( $this->type == 'pitm' && $this->version <= 1 ) || + ( $this->type == 'ipma' && $this->version <= 1 ) || + ( $this->type == 'ispe' && $this->version <= 0 ) || + ( $this->type == 'pixi' && $this->version <= 0 ) || + ( $this->type == 'iref' && $this->version <= 1 ) || + ( $this->type == 'auxC' && $this->version <= 0 ); + // Instead of considering this file as invalid, skip unparsable boxes. + if ( ! $is_parsable ) { + $this->type = 'unknownversion'; + } + } + // print_r( $this ); // Uncomment to print all boxes. + return FOUND; + } } //------------------------------------------------------------------------------ class Parser { - private $handle; // Input stream. - private $num_parsed_boxes = 0; - private $data_was_skipped = false; - public $features; - - function __construct( $handle ) { - $this->handle = $handle; - $this->features = new Features(); - } - - /** - * Parses an "ipco" box. - * - * "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. - * - * @param stream $handle The resource the box will be parsed from. - * @param int $num_remaining_bytes The number of bytes that should be available from the resource. - * @return Status FOUND on success or an error on failure. - */ - private function parse_ipco( $num_remaining_bytes ) { - $box_index = 1; // 1-based index. Used for iterating over properties. - do { - $box = new Box(); - $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); - if ( $status != FOUND ) { - return $status; - } - - if ( $box->type == 'ispe' ) { - // See ISO/IEC 23008-12:2017(E) 6.5.3.2 - if ( $box->content_size < 8 ) { - return INVALID; - } - if ( !( $data = read( $this->handle, 8 ) ) ) { - return TRUNCATED; - } - $width = read_big_endian( substr( $data, 0, 4 ), 4 ); - $height = read_big_endian( substr( $data, 4, 4 ), 4 ); - if ( $width == 0 || $height == 0 ) { - return INVALID; - } - if ( count( $this->features->dim_props ) <= MAX_FEATURES && - $box_index <= MAX_VALUE ) { - $dim_prop_count = count( $this->features->dim_props ); - $this->features->dim_props[$dim_prop_count] = new Dim_Prop(); - $this->features->dim_props[$dim_prop_count]->property_index = $box_index; - $this->features->dim_props[$dim_prop_count]->width = $width; - $this->features->dim_props[$dim_prop_count]->height = $height; - } else { - $this->data_was_skipped = true; - } - if ( !skip( $this->handle, $box->content_size - 8 ) ) { - return TRUNCATED; - } - } else if ( $box->type == 'pixi' ) { - // See ISO/IEC 23008-12:2017(E) 6.5.6.2 - if ( $box->content_size < 1 ) { - return INVALID; - } - if ( !( $data = read( $this->handle, 1 ) ) ) { - return TRUNCATED; - } - $num_channels = read_big_endian( $data, 1 ); - if ( $num_channels < 1 ) { - return INVALID; - } - if ( $box->content_size < 1 + $num_channels ) { - return INVALID; - } - if ( !( $data = read( $this->handle, 1 ) ) ) { - return TRUNCATED; - } - $bit_depth = read_big_endian( $data, 1 ); - if ( $bit_depth < 1 ) { - return INVALID; - } - for ( $i = 1; $i < $num_channels; ++$i ) { - if ( !( $data = read( $this->handle, 1 ) ) ) { - return TRUNCATED; - } - // Bit depth should be the same for all channels. - if ( read_big_endian( $data, 1 ) != $bit_depth ) { - return INVALID; - } - if ( $i > 32 ) { - return ABORTED; // Be reasonable. - } - } - if ( count( $this->features->chan_props ) <= MAX_FEATURES && - $box_index <= MAX_VALUE && $bit_depth <= MAX_VALUE && - $num_channels <= MAX_VALUE ) { - $chan_prop_count = count( $this->features->chan_props ); - $this->features->chan_props[$chan_prop_count] = new Chan_Prop(); - $this->features->chan_props[$chan_prop_count]->property_index = $box_index; - $this->features->chan_props[$chan_prop_count]->bit_depth = $bit_depth; - $this->features->chan_props[$chan_prop_count]->num_channels = $num_channels; - } else { - $this->data_was_skipped = true; - } - if ( !skip( $this->handle, $box->content_size - ( 1 + $num_channels ) ) ) { - return TRUNCATED; - } - } else if ( $box->type == 'av1C' ) { - // 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. - if ( $box->content_size < 3 ) { - return INVALID; - } - if ( !( $data = read( $this->handle, 3 ) ) ) { - return TRUNCATED; - } - $byte = read_big_endian( substr( $data, 2, 1 ), 1 ); - $high_bitdepth = ( $byte & 0x40 ) != 0; - $twelve_bit = ( $byte & 0x20 ) != 0; - $monochrome = ( $byte & 0x10 ) != 0; - if ( $twelve_bit && !$high_bitdepth ) { - return INVALID; - } - if ( count( $this->features->chan_props ) <= MAX_FEATURES && - $box_index <= MAX_VALUE ) { - $chan_prop_count = count( $this->features->chan_props ); - $this->features->chan_props[$chan_prop_count] = new Chan_Prop(); - $this->features->chan_props[$chan_prop_count]->property_index = $box_index; - $this->features->chan_props[$chan_prop_count]->bit_depth = - $high_bitdepth ? $twelve_bit ? 12 : 10 : 8; - $this->features->chan_props[$chan_prop_count]->num_channels = $monochrome ? 1 : 3; - } else { - $this->data_was_skipped = true; - } - if ( !skip( $this->handle, $box->content_size - 3 ) ) { - return TRUNCATED; - } - } else if ( $box->type == 'auxC' ) { - // See AV1 Image File Format (AVIF) 4 - // at https://aomediacodec.github.io/av1-avif/#auxiliary-images - $kAlphaStr = "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"; - $kAlphaStrLength = 44; // Includes terminating character. - if ( $box->content_size >= $kAlphaStrLength ) { - if ( !( $data = read( $this->handle, $kAlphaStrLength ) ) ) { - return TRUNCATED; - } - if ( substr( $data, 0, $kAlphaStrLength ) == $kAlphaStr ) { - // 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. - $this->features->has_alpha = true; - } - if ( !skip( $this->handle, $box->content_size - $kAlphaStrLength ) ) { - return TRUNCATED; - } - } else { - if ( !skip( $this->handle, $box->content_size ) ) { - return TRUNCATED; - } - } - } else { - if ( !skip( $this->handle, $box->content_size ) ) { - return TRUNCATED; - } - } - ++$box_index; - $num_remaining_bytes -= $box->size; - } while ( $num_remaining_bytes > 0 ); - return NOT_FOUND; - } - - /** - * Parses an "iprp" box. - * - * The "ipco" box contain the properties which are linked to items by the "ipma" box. - * - * @param stream $handle The resource the box will be parsed from. - * @param int $num_remaining_bytes The number of bytes that should be available from the resource. - * @return Status FOUND on success or an error on failure. - */ - private function parse_iprp( $num_remaining_bytes ) { - do { - $box = new Box(); - $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); - if ( $status != FOUND ) { - return $status; - } - - if ( $box->type == 'ipco' ) { - $status = $this->parse_ipco( $box->content_size ); - if ( $status != NOT_FOUND ) { - return $status; - } - } else if ( $box->type == 'ipma' ) { - // See ISO/IEC 23008-12:2017(E) 9.3.2 - $num_read_bytes = 4; - if ( $box->content_size < $num_read_bytes ) { - return INVALID; - } - if ( !( $data = read( $this->handle, $num_read_bytes ) ) ) { - return TRUNCATED; - } - $entry_count = read_big_endian( $data, 4 ); - $id_num_bytes = ( $box->version < 1 ) ? 2 : 4; - $index_num_bytes = ( $box->flags & 1 ) ? 2 : 1; - $essential_bit_mask = ( $box->flags & 1 ) ? 0x8000 : 0x80; - - for ( $entry = 0; $entry < $entry_count; ++$entry ) { - if ( $entry >= MAX_PROPS || - count( $this->features->props ) >= MAX_PROPS ) { - $this->data_was_skipped = true; - break; - } - $num_read_bytes += $id_num_bytes + 1; - if ( $box->content_size < $num_read_bytes ) { - return INVALID; - } - if ( !( $data = read( $this->handle, $id_num_bytes + 1 ) ) ) { - return TRUNCATED; - } - $item_id = read_big_endian( - substr( $data, 0, $id_num_bytes ), $id_num_bytes ); - $association_count = read_big_endian( - substr( $data, $id_num_bytes, 1 ), 1 ); - - for ( $property = 0; $property < $association_count; ++$property ) { - if ( $property >= MAX_PROPS || - count( $this->features->props ) >= MAX_PROPS ) { - $this->data_was_skipped = true; - break; - } - $num_read_bytes += $index_num_bytes; - if ( $box->content_size < $num_read_bytes ) { - return INVALID; - } - if ( !( $data = read( $this->handle, $index_num_bytes ) ) ) { - return TRUNCATED; - } - $value = read_big_endian( $data, $index_num_bytes ); - // $essential = ($value & $essential_bit_mask); // Unused. - $property_index = ( $value & ~$essential_bit_mask ); - if ( $property_index <= MAX_VALUE && $item_id <= MAX_VALUE ) { - $prop_count = count( $this->features->props ); - $this->features->props[$prop_count] = new Prop(); - $this->features->props[$prop_count]->property_index = $property_index; - $this->features->props[$prop_count]->item_id = $item_id; - } else { - $this->data_was_skipped = true; - } - } - if ( $property < $association_count ) { - break; // Do not read garbage. - } - } - - // If all features are available now, do not look further. - $status = $this->features->get_primary_item_features(); - if ( $status != NOT_FOUND ) { - return $status; - } - - // Mostly if 'data_was_skipped'. - if ( !skip( $this->handle, $box->content_size - $num_read_bytes ) ) { - return TRUNCATED; - } - } else { - if ( !skip( $this->handle, $box->content_size ) ) { - return TRUNCATED; - } - } - $num_remaining_bytes -= $box->size; - } while ( $num_remaining_bytes > 0 ); - return NOT_FOUND; - } - - /** - * Parses an "iref" box. - * - * 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. - * - * @param stream $handle The resource the box will be parsed from. - * @param int $num_remaining_bytes The number of bytes that should be available from the resource. - * @return Status FOUND on success or an error on failure. - */ - private function parse_iref( $num_remaining_bytes ) { - do { - $box = new Box(); - $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); - if ( $status != FOUND ) { - return $status; - } - - if ( $box->type == 'dimg' ) { - // See ISO/IEC 14496-12:2015(E) 8.11.12.2 - $num_bytes_per_id = ( $box->version == 0 ) ? 2 : 4; - $num_read_bytes = $num_bytes_per_id + 2; - if ( $box->content_size < $num_read_bytes ) { - return INVALID; - } - if ( !( $data = read( $this->handle, $num_read_bytes ) ) ) { - return TRUNCATED; - } - $from_item_id = read_big_endian( $data, $num_bytes_per_id ); - $reference_count = read_big_endian( substr( $data, $num_bytes_per_id, 2 ), 2 ); - - for ( $i = 0; $i < $reference_count; ++$i ) { - if ( $i >= MAX_TILES ) { - $this->data_was_skipped = true; - break; - } - $num_read_bytes += $num_bytes_per_id; - if ( $box->content_size < $num_read_bytes ) { - return INVALID; - } - if ( !( $data = read( $this->handle, $num_bytes_per_id ) ) ) { - return TRUNCATED; - } - $to_item_id = read_big_endian( $data, $num_bytes_per_id ); - $tile_count = count( $this->features->tiles ); - if ( $from_item_id <= MAX_VALUE && $to_item_id <= MAX_VALUE && - $tile_count < MAX_TILES ) { - $this->features->tiles[$tile_count] = new Tile(); - $this->features->tiles[$tile_count]->tile_item_id = $to_item_id; - $this->features->tiles[$tile_count]->parent_item_id = $from_item_id; - } else { - $this->data_was_skipped = true; - } - } - - // If all features are available now, do not look further. - $status = $this->features->get_primary_item_features(); - if ( $status != NOT_FOUND ) { - return $status; - } - - // Mostly if 'data_was_skipped'. - if ( !skip( $this->handle, $box->content_size - $num_read_bytes ) ) { - return TRUNCATED; - } - } else { - if ( !skip( $this->handle, $box->content_size ) ) { - return TRUNCATED; - } - } - $num_remaining_bytes -= $box->size; - } while ( $num_remaining_bytes > 0 ); - return NOT_FOUND; - } - - /** - * Parses a "meta" box. - * - * It looks for the primary item ID in the "pitm" box and recurses into other boxes - * to find its features. - * - * @param stream $handle The resource the box will be parsed from. - * @param int $num_remaining_bytes The number of bytes that should be available from the resource. - * @return Status FOUND on success or an error on failure. - */ - private function parse_meta( $num_remaining_bytes ) { - do { - $box = new Box(); - $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); - if ( $status != FOUND ) { - return $status; - } - - if ( $box->type == 'pitm' ) { - // See ISO/IEC 14496-12:2015(E) 8.11.4.2 - $num_bytes_per_id = ( $box->version == 0 ) ? 2 : 4; - if ( $num_bytes_per_id > $num_remaining_bytes ) { - return INVALID; - } - if ( !( $data = read( $this->handle, $num_bytes_per_id ) ) ) { - return TRUNCATED; - } - $primary_item_id = read_big_endian( $data, $num_bytes_per_id ); - if ( $primary_item_id > MAX_VALUE ) { - return ABORTED; - } - $this->features->has_primary_item = true; - $this->features->primary_item_id = $primary_item_id; - if ( !skip( $this->handle, $box->content_size - $num_bytes_per_id ) ) { - return TRUNCATED; - } - } else if ( $box->type == 'iprp' ) { - $status = $this->parse_iprp( $box->content_size ); - if ( $status != NOT_FOUND ) { - return $status; - } - } else if ( $box->type == 'iref' ) { - $status = $this->parse_iref( $box->content_size ); - if ( $status != NOT_FOUND ) { - return $status; - } - } else { - if ( !skip( $this->handle, $box->content_size ) ) { - return TRUNCATED; - } - } - $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". - return INVALID; - } - - /** - * Parses a file stream. - * - * The file type is checked through the "ftyp" box. - * - * @return bool True if the input stream is an AVIF bitstream or false. - */ - public function parse_ftyp() { - $box = new Box(); - $status = $box->parse( $this->handle, $this->num_parsed_boxes ); - if ( $status != FOUND ) { - return false; - } - - if ( $box->type != 'ftyp' ) { - return false; - } - // Iterate over brands. See ISO/IEC 14496-12:2012(E) 4.3.1 - if ( $box->content_size < 8 ) { - return false; - } - for ( $i = 0; $i + 4 <= $box->content_size; $i += 4 ) { - if ( !( $data = read( $this->handle, 4 ) ) ) { - return false; - } - if ( $i == 4 ) { - continue; // Skip minor_version. - } - if ( substr( $data, 0, 4 ) == 'avif' || substr( $data, 0, 4 ) == 'avis' ) { - return skip( $this->handle, $box->content_size - ( $i + 4 ) ); - } - if ( $i > 32 * 4 ) { - return false; // Be reasonable. - } - - } - return false; // No AVIF brand no good. - } - - /** - * Parses a file stream. - * - * Features are extracted from the "meta" box. - * - * @return bool True if the main features of the primary item were parsed or false. - */ - public function parse_file() { - $box = new Box(); - while ( $box->parse( $this->handle, $this->num_parsed_boxes ) == FOUND ) { - if ( $box->type === 'meta' ) { - if ( $this->parse_meta( $box->content_size ) != FOUND ) { - return false; - } - return true; - } - if ( !skip( $this->handle, $box->content_size ) ) { - return false; - } - } - return false; // No "meta" no good. - } + private $handle; // Input stream. + private $num_parsed_boxes = 0; + private $data_was_skipped = false; + public $features; + + function __construct( $handle ) { + $this->handle = $handle; + $this->features = new Features(); + } + + /** + * Parses an "ipco" box. + * + * "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. + * + * @param stream $handle The resource the box will be parsed from. + * @param int $num_remaining_bytes The number of bytes that should be available from the resource. + * @return Status FOUND on success or an error on failure. + */ + private function parse_ipco( $num_remaining_bytes ) { + $box_index = 1; // 1-based index. Used for iterating over properties. + do { + $box = new Box(); + $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); + if ( $status != FOUND ) { + return $status; + } + + if ( $box->type == 'ispe' ) { + // See ISO/IEC 23008-12:2017(E) 6.5.3.2 + if ( $box->content_size < 8 ) { + return INVALID; + } + if ( ! ( $data = read( $this->handle, 8 ) ) ) { + return TRUNCATED; + } + $width = read_big_endian( substr( $data, 0, 4 ), 4 ); + $height = read_big_endian( substr( $data, 4, 4 ), 4 ); + if ( $width == 0 || $height == 0 ) { + return INVALID; + } + if ( count( $this->features->dim_props ) <= MAX_FEATURES && + $box_index <= MAX_VALUE ) { + $dim_prop_count = count( $this->features->dim_props ); + $this->features->dim_props[ $dim_prop_count ] = new Dim_Prop(); + $this->features->dim_props[ $dim_prop_count ]->property_index = $box_index; + $this->features->dim_props[ $dim_prop_count ]->width = $width; + $this->features->dim_props[ $dim_prop_count ]->height = $height; + } else { + $this->data_was_skipped = true; + } + if ( ! skip( $this->handle, $box->content_size - 8 ) ) { + return TRUNCATED; + } + } elseif ( $box->type == 'pixi' ) { + // See ISO/IEC 23008-12:2017(E) 6.5.6.2 + if ( $box->content_size < 1 ) { + return INVALID; + } + if ( ! ( $data = read( $this->handle, 1 ) ) ) { + return TRUNCATED; + } + $num_channels = read_big_endian( $data, 1 ); + if ( $num_channels < 1 ) { + return INVALID; + } + if ( $box->content_size < 1 + $num_channels ) { + return INVALID; + } + if ( ! ( $data = read( $this->handle, 1 ) ) ) { + return TRUNCATED; + } + $bit_depth = read_big_endian( $data, 1 ); + if ( $bit_depth < 1 ) { + return INVALID; + } + for ( $i = 1; $i < $num_channels; ++$i ) { + if ( ! ( $data = read( $this->handle, 1 ) ) ) { + return TRUNCATED; + } + // Bit depth should be the same for all channels. + if ( read_big_endian( $data, 1 ) != $bit_depth ) { + return INVALID; + } + if ( $i > 32 ) { + return ABORTED; // Be reasonable. + } + } + if ( count( $this->features->chan_props ) <= MAX_FEATURES && + $box_index <= MAX_VALUE && $bit_depth <= MAX_VALUE && + $num_channels <= MAX_VALUE ) { + $chan_prop_count = count( $this->features->chan_props ); + $this->features->chan_props[ $chan_prop_count ] = new Chan_Prop(); + $this->features->chan_props[ $chan_prop_count ]->property_index = $box_index; + $this->features->chan_props[ $chan_prop_count ]->bit_depth = $bit_depth; + $this->features->chan_props[ $chan_prop_count ]->num_channels = $num_channels; + } else { + $this->data_was_skipped = true; + } + if ( ! skip( $this->handle, $box->content_size - ( 1 + $num_channels ) ) ) { + return TRUNCATED; + } + } elseif ( $box->type == 'av1C' ) { + // 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. + if ( $box->content_size < 3 ) { + return INVALID; + } + if ( ! ( $data = read( $this->handle, 3 ) ) ) { + return TRUNCATED; + } + $byte = read_big_endian( substr( $data, 2, 1 ), 1 ); + $high_bitdepth = ( $byte & 0x40 ) != 0; + $twelve_bit = ( $byte & 0x20 ) != 0; + $monochrome = ( $byte & 0x10 ) != 0; + if ( $twelve_bit && ! $high_bitdepth ) { + return INVALID; + } + if ( count( $this->features->chan_props ) <= MAX_FEATURES && + $box_index <= MAX_VALUE ) { + $chan_prop_count = count( $this->features->chan_props ); + $this->features->chan_props[ $chan_prop_count ] = new Chan_Prop(); + $this->features->chan_props[ $chan_prop_count ]->property_index = $box_index; + $this->features->chan_props[ $chan_prop_count ]->bit_depth = + $high_bitdepth ? $twelve_bit ? 12 : 10 : 8; + $this->features->chan_props[ $chan_prop_count ]->num_channels = $monochrome ? 1 : 3; + } else { + $this->data_was_skipped = true; + } + if ( ! skip( $this->handle, $box->content_size - 3 ) ) { + return TRUNCATED; + } + } elseif ( $box->type == 'auxC' ) { + // See AV1 Image File Format (AVIF) 4 + // at https://aomediacodec.github.io/av1-avif/#auxiliary-images + $kAlphaStr = 'urn:mpeg:mpegB:cicp:systems:auxiliary:alpha'; + $kAlphaStrLength = 44; // Includes terminating character. + if ( $box->content_size >= $kAlphaStrLength ) { + if ( ! ( $data = read( $this->handle, $kAlphaStrLength ) ) ) { + return TRUNCATED; + } + if ( substr( $data, 0, $kAlphaStrLength ) == $kAlphaStr ) { + // 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. + $this->features->has_alpha = true; + } + if ( ! skip( $this->handle, $box->content_size - $kAlphaStrLength ) ) { + return TRUNCATED; + } + } else { + if ( ! skip( $this->handle, $box->content_size ) ) { + return TRUNCATED; + } + } + } else { + if ( ! skip( $this->handle, $box->content_size ) ) { + return TRUNCATED; + } + } + ++$box_index; + $num_remaining_bytes -= $box->size; + } while ( $num_remaining_bytes > 0 ); + return NOT_FOUND; + } + + /** + * Parses an "iprp" box. + * + * The "ipco" box contain the properties which are linked to items by the "ipma" box. + * + * @param stream $handle The resource the box will be parsed from. + * @param int $num_remaining_bytes The number of bytes that should be available from the resource. + * @return Status FOUND on success or an error on failure. + */ + private function parse_iprp( $num_remaining_bytes ) { + do { + $box = new Box(); + $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); + if ( $status != FOUND ) { + return $status; + } + + if ( $box->type == 'ipco' ) { + $status = $this->parse_ipco( $box->content_size ); + if ( $status != NOT_FOUND ) { + return $status; + } + } elseif ( $box->type == 'ipma' ) { + // See ISO/IEC 23008-12:2017(E) 9.3.2 + $num_read_bytes = 4; + if ( $box->content_size < $num_read_bytes ) { + return INVALID; + } + if ( ! ( $data = read( $this->handle, $num_read_bytes ) ) ) { + return TRUNCATED; + } + $entry_count = read_big_endian( $data, 4 ); + $id_num_bytes = ( $box->version < 1 ) ? 2 : 4; + $index_num_bytes = ( $box->flags & 1 ) ? 2 : 1; + $essential_bit_mask = ( $box->flags & 1 ) ? 0x8000 : 0x80; + + for ( $entry = 0; $entry < $entry_count; ++$entry ) { + if ( $entry >= MAX_PROPS || + count( $this->features->props ) >= MAX_PROPS ) { + $this->data_was_skipped = true; + break; + } + $num_read_bytes += $id_num_bytes + 1; + if ( $box->content_size < $num_read_bytes ) { + return INVALID; + } + if ( ! ( $data = read( $this->handle, $id_num_bytes + 1 ) ) ) { + return TRUNCATED; + } + $item_id = read_big_endian( + substr( $data, 0, $id_num_bytes ), + $id_num_bytes + ); + $association_count = read_big_endian( + substr( $data, $id_num_bytes, 1 ), + 1 + ); + + for ( $property = 0; $property < $association_count; ++$property ) { + if ( $property >= MAX_PROPS || + count( $this->features->props ) >= MAX_PROPS ) { + $this->data_was_skipped = true; + break; + } + $num_read_bytes += $index_num_bytes; + if ( $box->content_size < $num_read_bytes ) { + return INVALID; + } + if ( ! ( $data = read( $this->handle, $index_num_bytes ) ) ) { + return TRUNCATED; + } + $value = read_big_endian( $data, $index_num_bytes ); + // $essential = ($value & $essential_bit_mask); // Unused. + $property_index = ( $value & ~$essential_bit_mask ); + if ( $property_index <= MAX_VALUE && $item_id <= MAX_VALUE ) { + $prop_count = count( $this->features->props ); + $this->features->props[ $prop_count ] = new Prop(); + $this->features->props[ $prop_count ]->property_index = $property_index; + $this->features->props[ $prop_count ]->item_id = $item_id; + } else { + $this->data_was_skipped = true; + } + } + if ( $property < $association_count ) { + break; // Do not read garbage. + } + } + + // If all features are available now, do not look further. + $status = $this->features->get_primary_item_features(); + if ( $status != NOT_FOUND ) { + return $status; + } + + // Mostly if 'data_was_skipped'. + if ( ! skip( $this->handle, $box->content_size - $num_read_bytes ) ) { + return TRUNCATED; + } + } else { + if ( ! skip( $this->handle, $box->content_size ) ) { + return TRUNCATED; + } + } + $num_remaining_bytes -= $box->size; + } while ( $num_remaining_bytes > 0 ); + return NOT_FOUND; + } + + /** + * Parses an "iref" box. + * + * 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. + * + * @param stream $handle The resource the box will be parsed from. + * @param int $num_remaining_bytes The number of bytes that should be available from the resource. + * @return Status FOUND on success or an error on failure. + */ + private function parse_iref( $num_remaining_bytes ) { + do { + $box = new Box(); + $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); + if ( $status != FOUND ) { + return $status; + } + + if ( $box->type == 'dimg' ) { + // See ISO/IEC 14496-12:2015(E) 8.11.12.2 + $num_bytes_per_id = ( $box->version == 0 ) ? 2 : 4; + $num_read_bytes = $num_bytes_per_id + 2; + if ( $box->content_size < $num_read_bytes ) { + return INVALID; + } + if ( ! ( $data = read( $this->handle, $num_read_bytes ) ) ) { + return TRUNCATED; + } + $from_item_id = read_big_endian( $data, $num_bytes_per_id ); + $reference_count = read_big_endian( substr( $data, $num_bytes_per_id, 2 ), 2 ); + + for ( $i = 0; $i < $reference_count; ++$i ) { + if ( $i >= MAX_TILES ) { + $this->data_was_skipped = true; + break; + } + $num_read_bytes += $num_bytes_per_id; + if ( $box->content_size < $num_read_bytes ) { + return INVALID; + } + if ( ! ( $data = read( $this->handle, $num_bytes_per_id ) ) ) { + return TRUNCATED; + } + $to_item_id = read_big_endian( $data, $num_bytes_per_id ); + $tile_count = count( $this->features->tiles ); + if ( $from_item_id <= MAX_VALUE && $to_item_id <= MAX_VALUE && + $tile_count < MAX_TILES ) { + $this->features->tiles[ $tile_count ] = new Tile(); + $this->features->tiles[ $tile_count ]->tile_item_id = $to_item_id; + $this->features->tiles[ $tile_count ]->parent_item_id = $from_item_id; + } else { + $this->data_was_skipped = true; + } + } + + // If all features are available now, do not look further. + $status = $this->features->get_primary_item_features(); + if ( $status != NOT_FOUND ) { + return $status; + } + + // Mostly if 'data_was_skipped'. + if ( ! skip( $this->handle, $box->content_size - $num_read_bytes ) ) { + return TRUNCATED; + } + } else { + if ( ! skip( $this->handle, $box->content_size ) ) { + return TRUNCATED; + } + } + $num_remaining_bytes -= $box->size; + } while ( $num_remaining_bytes > 0 ); + return NOT_FOUND; + } + + /** + * Parses a "meta" box. + * + * It looks for the primary item ID in the "pitm" box and recurses into other boxes + * to find its features. + * + * @param stream $handle The resource the box will be parsed from. + * @param int $num_remaining_bytes The number of bytes that should be available from the resource. + * @return Status FOUND on success or an error on failure. + */ + private function parse_meta( $num_remaining_bytes ) { + do { + $box = new Box(); + $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); + if ( $status != FOUND ) { + return $status; + } + + if ( $box->type == 'pitm' ) { + // See ISO/IEC 14496-12:2015(E) 8.11.4.2 + $num_bytes_per_id = ( $box->version == 0 ) ? 2 : 4; + if ( $num_bytes_per_id > $num_remaining_bytes ) { + return INVALID; + } + if ( ! ( $data = read( $this->handle, $num_bytes_per_id ) ) ) { + return TRUNCATED; + } + $primary_item_id = read_big_endian( $data, $num_bytes_per_id ); + if ( $primary_item_id > MAX_VALUE ) { + return ABORTED; + } + $this->features->has_primary_item = true; + $this->features->primary_item_id = $primary_item_id; + if ( ! skip( $this->handle, $box->content_size - $num_bytes_per_id ) ) { + return TRUNCATED; + } + } elseif ( $box->type == 'iprp' ) { + $status = $this->parse_iprp( $box->content_size ); + if ( $status != NOT_FOUND ) { + return $status; + } + } elseif ( $box->type == 'iref' ) { + $status = $this->parse_iref( $box->content_size ); + if ( $status != NOT_FOUND ) { + return $status; + } + } else { + if ( ! skip( $this->handle, $box->content_size ) ) { + return TRUNCATED; + } + } + $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". + return INVALID; + } + + /** + * Parses a file stream. + * + * The file type is checked through the "ftyp" box. + * + * @return bool True if the input stream is an AVIF bitstream or false. + */ + public function parse_ftyp() { + $box = new Box(); + $status = $box->parse( $this->handle, $this->num_parsed_boxes ); + if ( $status != FOUND ) { + return false; + } + + if ( $box->type != 'ftyp' ) { + return false; + } + // Iterate over brands. See ISO/IEC 14496-12:2012(E) 4.3.1 + if ( $box->content_size < 8 ) { + return false; + } + for ( $i = 0; $i + 4 <= $box->content_size; $i += 4 ) { + if ( ! ( $data = read( $this->handle, 4 ) ) ) { + return false; + } + if ( $i == 4 ) { + continue; // Skip minor_version. + } + if ( substr( $data, 0, 4 ) == 'avif' || substr( $data, 0, 4 ) == 'avis' ) { + return skip( $this->handle, $box->content_size - ( $i + 4 ) ); + } + if ( $i > 32 * 4 ) { + return false; // Be reasonable. + } + } + return false; // No AVIF brand no good. + } + + /** + * Parses a file stream. + * + * Features are extracted from the "meta" box. + * + * @return bool True if the main features of the primary item were parsed or false. + */ + public function parse_file() { + $box = new Box(); + while ( $box->parse( $this->handle, $this->num_parsed_boxes ) == FOUND ) { + if ( $box->type === 'meta' ) { + if ( $this->parse_meta( $box->content_size ) != FOUND ) { + return false; + } + return true; + } + if ( ! skip( $this->handle, $box->content_size ) ) { + return false; + } + } + return false; // No "meta" no good. + } } From bb38f1943d78e1d046929b70160165d56f33b805 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 16 Jun 2023 07:20:14 -0600 Subject: [PATCH 08/79] more phpcbf --- src/wp-includes/class-wp-image-editor-gd.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-image-editor-gd.php b/src/wp-includes/class-wp-image-editor-gd.php index 6bd9d9676c4a2..f232ec06c4c6e 100644 --- a/src/wp-includes/class-wp-image-editor-gd.php +++ b/src/wp-includes/class-wp-image-editor-gd.php @@ -73,7 +73,7 @@ public static function supports_mime_type( $mime_type ) { return ( $image_types & IMG_WEBP ) != 0; case 'image/avif': return ( $image_types & IMG_AVIF ) != 0; - } + } return false; } @@ -562,11 +562,13 @@ public function stream( $mime_type = null ) { header( 'Content-Type: image/webp' ); return imagewebp( $this->image, null, $this->get_quality() ); } + // Fall back to the default if WebP isn't supported. case 'image/avif': if ( function_exists( 'imageavif' ) ) { header( 'Content-Type: image/avif' ); return imageavif( $this->image, null, $this->get_quality() ); } + // Fall back to the default if AVIF isn't supported. default: header( 'Content-Type: image/jpeg' ); return imagejpeg( $this->image, null, $this->get_quality() ); From f1a90ccd3bbf712293f63a65a7779c0b242a3770 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 16 Jun 2023 07:26:44 -0600 Subject: [PATCH 09/79] yoda conditionals, other small phpcs fixes --- src/wp-includes/class-avif-info.php | 14 ++++++-------- src/wp-includes/media.php | 3 +-- .../class-wp-rest-attachments-controller.php | 2 +- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/wp-includes/class-avif-info.php b/src/wp-includes/class-avif-info.php index b1a26e35967f3..ad0f9bc6477f8 100644 --- a/src/wp-includes/class-avif-info.php +++ b/src/wp-includes/class-avif-info.php @@ -34,11 +34,11 @@ * @return int Value. */ function read_big_endian( $input, $num_bytes ) { - if ( $num_bytes == 1 ) { + if ( 1 === $num_bytes ) { return unpack( 'C', $input ) [1]; - } elseif ( $num_bytes == 2 ) { + } elseif ( 2 === $num_bytes ) { return unpack( 'n', $input ) [1]; - } elseif ( $num_bytes == 3 ) { + } elseif ( 3 === $num_bytes ) { $bytes = unpack( 'C3', $input ); return ( $bytes[1] << 16 ) | ( $bytes[2] << 8 ) | $bytes[3]; } else { // $num_bytes is 4 @@ -57,7 +57,7 @@ function read_big_endian( $input, $num_bytes ) { */ function read( $handle, $num_bytes ) { $data = fread( $handle, $num_bytes ); - return ( $data !== false && strlen( $data ) >= $num_bytes ) ? $data : false; + return ( FALSE !== $data && strlen( $data ) >= $num_bytes ) ? $data : false; } /** @@ -133,16 +133,14 @@ private function get_item_features( $target_item_id, $tile_depth ) { // Retrieve the width and height of the primary item if not already done. if ( $target_item_id == $this->primary_item_id && - ( $this->primary_item_features['width'] == UNDEFINED || - $this->primary_item_features['height'] == UNDEFINED ) ) { + ( UNDEFINED === $this->primary_item_features['width'] || UNDEFINED === $this->primary_item_features['height'] ) ) { foreach ( $this->dim_props as $dim_prop ) { if ( $dim_prop->property_index != $prop->property_index ) { continue; } $this->primary_item_features['width'] = $dim_prop->width; $this->primary_item_features['height'] = $dim_prop->height; - if ( $this->primary_item_features['bit_depth'] != UNDEFINED && - $this->primary_item_features['num_channels'] != UNDEFINED ) { + if ( UNDEFINED !== $this->primary_item_features['bit_depth'] && UNDEFINED !== $this->primary_item_features['num_channels'] ) { return FOUND; } break; diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index c432f25903185..6d073afc7ef08 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -5426,7 +5426,6 @@ function wp_getimagesize( $filename, array &$image_info = null ) { } } - // The image could not be parsed. return false; } @@ -5474,7 +5473,7 @@ function wp_get_avif_info( $filename ) { 'width' => false, 'height' => false, 'bit_depth' => false, - 'num_channels' => false + 'num_channels' => false, ); $handle = fopen( $filename, 'rb' ); diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php index 44102c2d236c7..827eeafa6f958 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php @@ -446,7 +446,7 @@ public function edit_media_item( $request ) { ); } - $supported_types = array( 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif' ); + $supported_types = array( 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif' ); $mime_type = get_post_mime_type( $attachment_id ); if ( ! in_array( $mime_type, $supported_types, true ) ) { return new WP_Error( From 41762ff4584936983152e0d2dfc05aa4ec98961f Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 16 Jun 2023 07:38:09 -0600 Subject: [PATCH 10/79] Restore avif helper original and exclude from phpcs --- phpcs.xml.dist | 1 + src/wp-includes/class-avif-info.php | 1391 +++++++++++++-------------- 2 files changed, 694 insertions(+), 698 deletions(-) diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 453ef5c794745..61ea6129e1690 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -117,6 +117,7 @@ /src/wp-includes/class-simplepie\.php /src/wp-includes/class-snoopy\.php /src/wp-includes/class-wp-block-parser\.php + /src/wp-includes/class-avif-info\.php /src/wp-includes/deprecated\.php /src/wp-includes/ms-deprecated\.php /src/wp-includes/pluggable-deprecated\.php diff --git a/src/wp-includes/class-avif-info.php b/src/wp-includes/class-avif-info.php index ad0f9bc6477f8..84a2e9333de08 100644 --- a/src/wp-includes/class-avif-info.php +++ b/src/wp-includes/class-avif-info.php @@ -34,18 +34,18 @@ * @return int Value. */ function read_big_endian( $input, $num_bytes ) { - if ( 1 === $num_bytes ) { - return unpack( 'C', $input ) [1]; - } elseif ( 2 === $num_bytes ) { - return unpack( 'n', $input ) [1]; - } elseif ( 3 === $num_bytes ) { - $bytes = unpack( 'C3', $input ); - return ( $bytes[1] << 16 ) | ( $bytes[2] << 8 ) | $bytes[3]; - } else { // $num_bytes is 4 - // This might fail to read unsigned values >= 2^31 on 32-bit systems. - // See https://www.php.net/manual/en/function.unpack.php#106041 - return unpack( 'N', $input ) [1]; - } + if ( $num_bytes == 1 ) { + return unpack( 'C', $input ) [1]; + } else if ( $num_bytes == 2 ) { + return unpack( 'n', $input ) [1]; + } else if ( $num_bytes == 3 ) { + $bytes = unpack( 'C3', $input ); + return ( $bytes[1] << 16 ) | ( $bytes[2] << 8 ) | $bytes[3]; + } else { // $num_bytes is 4 + // This might fail to read unsigned values >= 2^31 on 32-bit systems. + // See https://www.php.net/manual/en/function.unpack.php#106041 + return unpack( 'N', $input ) [1]; + } } /** @@ -56,8 +56,8 @@ function read_big_endian( $input, $num_bytes ) { * @return binary string|false The raw bytes or false on failure. */ function read( $handle, $num_bytes ) { - $data = fread( $handle, $num_bytes ); - return ( FALSE !== $data && strlen( $data ) >= $num_bytes ) ? $data : false; + $data = fread( $handle, $num_bytes ); + return ( $data !== false && strlen( $data ) >= $num_bytes ) ? $data : false; } /** @@ -69,712 +69,707 @@ function read( $handle, $num_bytes ) { */ // Skips 'num_bytes' from the 'stream'. 'num_bytes' can be zero. function skip( $handle, $num_bytes ) { - return ( fseek( $handle, $num_bytes, SEEK_CUR ) == 0 ); + return ( fseek( $handle, $num_bytes, SEEK_CUR ) == 0 ); } //------------------------------------------------------------------------------ // Features are parsed into temporary property associations. -class Tile { - // Tile item id <-> parent item id associations. - public $tile_item_id; - public $parent_item_id; +class Tile { // Tile item id <-> parent item id associations. + public $tile_item_id; + public $parent_item_id; } -class Prop { - // Property index <-> item id associations. - public $property_index; - public $item_id; +class Prop { // Property index <-> item id associations. + public $property_index; + public $item_id; } -class Dim_Prop { - // Property <-> features associations. - public $property_index; - public $width; - public $height; +class Dim_Prop { // Property <-> features associations. + public $property_index; + public $width; + public $height; } -class Chan_Prop { - // Property <-> features associations. - public $property_index; - public $bit_depth; - public $num_channels; +class Chan_Prop { // Property <-> features associations. + public $property_index; + public $bit_depth; + public $num_channels; } class Features { - public $has_primary_item = false; // True if "pitm" was parsed. - public $has_alpha = false; // True if an alpha "auxC" was parsed. - public $primary_item_id; - public $primary_item_features = array( // Deduced from the data below. - 'width' => UNDEFINED, // In number of pixels. - 'height' => UNDEFINED, // Ignores mirror and rotation. - 'bit_depth' => UNDEFINED, // Likely 8, 10 or 12 bits per channel per pixel. - 'num_channels' => UNDEFINED, // Likely 1, 2, 3 or 4 channels: - // (1 monochrome or 3 colors) + (0 or 1 alpha) - ); - - public $tiles = array(); // Tile[] - public $props = array(); // Prop[] - public $dim_props = array(); // Dim_Prop[] - public $chan_props = array(); // Chan_Prop[] - - /** - * Binds the width, height, bit depth and number of channels from stored internal features. - * - * @param int $target_item_id Id of the item whose features will be bound. - * @param int $tile_depth Maximum recursion to search within tile-parent relations. - * @return Status FOUND on success or NOT_FOUND on failure. - */ - private function get_item_features( $target_item_id, $tile_depth ) { - foreach ( $this->props as $prop ) { - if ( $prop->item_id != $target_item_id ) { - continue; - } - - // Retrieve the width and height of the primary item if not already done. - if ( $target_item_id == $this->primary_item_id && - ( UNDEFINED === $this->primary_item_features['width'] || UNDEFINED === $this->primary_item_features['height'] ) ) { - foreach ( $this->dim_props as $dim_prop ) { - if ( $dim_prop->property_index != $prop->property_index ) { - continue; - } - $this->primary_item_features['width'] = $dim_prop->width; - $this->primary_item_features['height'] = $dim_prop->height; - if ( UNDEFINED !== $this->primary_item_features['bit_depth'] && UNDEFINED !== $this->primary_item_features['num_channels'] ) { - return FOUND; - } - break; - } - } - // Retrieve the bit depth and number of channels of the target item if not - // already done. - if ( $this->primary_item_features['bit_depth'] == UNDEFINED || - $this->primary_item_features['num_channels'] == UNDEFINED ) { - foreach ( $this->chan_props as $chan_prop ) { - if ( $chan_prop->property_index != $prop->property_index ) { - continue; - } - $this->primary_item_features['bit_depth'] = $chan_prop->bit_depth; - $this->primary_item_features['num_channels'] = $chan_prop->num_channels; - if ( $this->primary_item_features['width'] != UNDEFINED && - $this->primary_item_features['height'] != UNDEFINED ) { - return FOUND; - } - break; - } - } - } - - // Check for the bit_depth and num_channels in a tile if not yet found. - if ( $tile_depth < 3 ) { - foreach ( $this->tiles as $tile ) { - if ( $tile->parent_item_id != $target_item_id ) { - continue; - } - $status = get_item_features( $tile->tile_item_id, $tile_depth + 1 ); - if ( $status != NOT_FOUND ) { - return $status; - } - } - } - return NOT_FOUND; - } - - /** - * Finds the width, height, bit depth and number of channels of the primary item. - * - * @return Status FOUND on success or NOT_FOUND on failure. - */ - public function get_primary_item_features() { - // Nothing to do without the primary item ID. - if ( ! $this->has_primary_item ) { - return NOT_FOUND; - } - // Early exit. - if ( empty( $this->dim_props ) || empty( $this->chan_props ) ) { - return NOT_FOUND; - } - $status = $this->get_item_features( $this->primary_item_id, /*tile_depth=*/ 0 ); - if ( $status != FOUND ) { - return $status; - } - - // "auxC" is parsed before the "ipma" properties so it is known now, if any. - if ( $this->has_alpha ) { - ++$this->primary_item_features['num_channels']; - } - return FOUND; - } + public $has_primary_item = false; // True if "pitm" was parsed. + public $has_alpha = false; // True if an alpha "auxC" was parsed. + public $primary_item_id; + public $primary_item_features = array( // Deduced from the data below. + 'width' => UNDEFINED, // In number of pixels. + 'height' => UNDEFINED, // Ignores mirror and rotation. + 'bit_depth' => UNDEFINED, // Likely 8, 10 or 12 bits per channel per pixel. + 'num_channels' => UNDEFINED // Likely 1, 2, 3 or 4 channels: + // (1 monochrome or 3 colors) + (0 or 1 alpha) + ); + + public $tiles = array(); // Tile[] + public $props = array(); // Prop[] + public $dim_props = array(); // Dim_Prop[] + public $chan_props = array(); // Chan_Prop[] + + /** + * Binds the width, height, bit depth and number of channels from stored internal features. + * + * @param int $target_item_id Id of the item whose features will be bound. + * @param int $tile_depth Maximum recursion to search within tile-parent relations. + * @return Status FOUND on success or NOT_FOUND on failure. + */ + private function get_item_features( $target_item_id, $tile_depth ) { + foreach ( $this->props as $prop ) { + if ( $prop->item_id != $target_item_id ) { + continue; + } + + // Retrieve the width and height of the primary item if not already done. + if ( $target_item_id == $this->primary_item_id && + ( $this->primary_item_features['width'] == UNDEFINED || + $this->primary_item_features['height'] == UNDEFINED ) ) { + foreach ( $this->dim_props as $dim_prop ) { + if ( $dim_prop->property_index != $prop->property_index ) { + continue; + } + $this->primary_item_features['width'] = $dim_prop->width; + $this->primary_item_features['height'] = $dim_prop->height; + if ( $this->primary_item_features['bit_depth'] != UNDEFINED && + $this->primary_item_features['num_channels'] != UNDEFINED ) { + return FOUND; + } + break; + } + } + // Retrieve the bit depth and number of channels of the target item if not + // already done. + if ( $this->primary_item_features['bit_depth'] == UNDEFINED || + $this->primary_item_features['num_channels'] == UNDEFINED ) { + foreach ( $this->chan_props as $chan_prop ) { + if ( $chan_prop->property_index != $prop->property_index ) { + continue; + } + $this->primary_item_features['bit_depth'] = $chan_prop->bit_depth; + $this->primary_item_features['num_channels'] = $chan_prop->num_channels; + if ( $this->primary_item_features['width'] != UNDEFINED && + $this->primary_item_features['height'] != UNDEFINED ) { + return FOUND; + } + break; + } + } + } + + // Check for the bit_depth and num_channels in a tile if not yet found. + if ( $tile_depth < 3 ) { + foreach ( $this->tiles as $tile ) { + if ( $tile->parent_item_id != $target_item_id ) { + continue; + } + $status = get_item_features( $tile->tile_item_id, $tile_depth + 1 ); + if ( $status != NOT_FOUND ) { + return $status; + } + } + } + return NOT_FOUND; + } + + /** + * Finds the width, height, bit depth and number of channels of the primary item. + * + * @return Status FOUND on success or NOT_FOUND on failure. + */ + public function get_primary_item_features() { + // Nothing to do without the primary item ID. + if ( !$this->has_primary_item ) { + return NOT_FOUND; + } + // Early exit. + if ( empty( $this->dim_props ) || empty( $this->chan_props ) ) { + return NOT_FOUND; + } + $status = $this->get_item_features( $this->primary_item_id, /*tile_depth=*/ 0 ); + if ( $status != FOUND ) { + return $status; + } + + // "auxC" is parsed before the "ipma" properties so it is known now, if any. + if ( $this->has_alpha ) { + ++$this->primary_item_features['num_channels']; + } + return FOUND; + } } //------------------------------------------------------------------------------ class Box { - public $size; // In bytes. - public $type; // Four characters. - public $version; // 0 or actual version if this is a full box. - public $flags; // 0 or actual value if this is a full box. - public $content_size; // 'size' minus the header size. - - /** - * Reads the box header. - * - * @param stream $handle The resource the header will be parsed from. - * @param int $num_parsed_boxes The total number of parsed boxes. Prevents timeouts. - * @param int $num_remaining_bytes The number of bytes that should be available from the resource. - * @return Status FOUND on success or an error on failure. - */ - public function parse( $handle, &$num_parsed_boxes, $num_remaining_bytes = MAX_SIZE ) { - // See ISO/IEC 14496-12:2012(E) 4.2 - $header_size = 8; // box 32b size + 32b type (at least) - if ( $header_size > $num_remaining_bytes ) { - return INVALID; - } - if ( ! ( $data = read( $handle, 8 ) ) ) { - return TRUNCATED; - } - $this->size = read_big_endian( $data, 4 ); - $this->type = substr( $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 ( $this->size == 1 ) { - $header_size += 8; - if ( $header_size > $num_remaining_bytes ) { - return INVALID; - } - if ( ! ( $data = read( $handle, 8 ) ) ) { - return TRUNCATED; - } - // Stop the parsing if any box has a size greater than 4GB. - if ( read_big_endian( $data, 4 ) != 0 ) { - return ABORTED; - } - // Read the 32 least-significant bits. - $this->size = read_big_endian( substr( $data, 4, 4 ), 4 ); - } elseif ( $this->size == 0 ) { - $this->size = $num_remaining_bytes; - } - if ( $this->size < $header_size ) { - return INVALID; - } - if ( $this->size > $num_remaining_bytes ) { - return INVALID; - } - - $has_fullbox_header = $this->type == 'meta' || $this->type == 'pitm' || - $this->type == 'ipma' || $this->type == 'ispe' || - $this->type == 'pixi' || $this->type == 'iref' || - $this->type == 'auxC'; - if ( $has_fullbox_header ) { - $header_size += 4; - } - if ( $this->size < $header_size ) { - return INVALID; - } - $this->content_size = $this->size - $header_size; - // Avoid timeouts. The maximum number of parsed boxes is arbitrary. - ++$num_parsed_boxes; - if ( $num_parsed_boxes >= MAX_NUM_BOXES ) { - return ABORTED; - } - - $this->version = 0; - $this->flags = 0; - if ( $has_fullbox_header ) { - if ( ! ( $data = read( $handle, 4 ) ) ) { - return TRUNCATED; - } - $this->version = read_big_endian( $data, 1 ); - $this->flags = read_big_endian( substr( $data, 1, 3 ), 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). - $is_parsable = ( $this->type == 'meta' && $this->version <= 0 ) || - ( $this->type == 'pitm' && $this->version <= 1 ) || - ( $this->type == 'ipma' && $this->version <= 1 ) || - ( $this->type == 'ispe' && $this->version <= 0 ) || - ( $this->type == 'pixi' && $this->version <= 0 ) || - ( $this->type == 'iref' && $this->version <= 1 ) || - ( $this->type == 'auxC' && $this->version <= 0 ); - // Instead of considering this file as invalid, skip unparsable boxes. - if ( ! $is_parsable ) { - $this->type = 'unknownversion'; - } - } - // print_r( $this ); // Uncomment to print all boxes. - return FOUND; - } + public $size; // In bytes. + public $type; // Four characters. + public $version; // 0 or actual version if this is a full box. + public $flags; // 0 or actual value if this is a full box. + public $content_size; // 'size' minus the header size. + + /** + * Reads the box header. + * + * @param stream $handle The resource the header will be parsed from. + * @param int $num_parsed_boxes The total number of parsed boxes. Prevents timeouts. + * @param int $num_remaining_bytes The number of bytes that should be available from the resource. + * @return Status FOUND on success or an error on failure. + */ + public function parse( $handle, &$num_parsed_boxes, $num_remaining_bytes = MAX_SIZE ) { + // See ISO/IEC 14496-12:2012(E) 4.2 + $header_size = 8; // box 32b size + 32b type (at least) + if ( $header_size > $num_remaining_bytes ) { + return INVALID; + } + if ( !( $data = read( $handle, 8 ) ) ) { + return TRUNCATED; + } + $this->size = read_big_endian( $data, 4 ); + $this->type = substr( $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 ( $this->size == 1 ) { + $header_size += 8; + if ( $header_size > $num_remaining_bytes ) { + return INVALID; + } + if ( !( $data = read( $handle, 8 ) ) ) { + return TRUNCATED; + } + // Stop the parsing if any box has a size greater than 4GB. + if ( read_big_endian( $data, 4 ) != 0 ) { + return ABORTED; + } + // Read the 32 least-significant bits. + $this->size = read_big_endian( substr( $data, 4, 4 ), 4 ); + } else if ( $this->size == 0 ) { + $this->size = $num_remaining_bytes; + } + if ( $this->size < $header_size ) { + return INVALID; + } + if ( $this->size > $num_remaining_bytes ) { + return INVALID; + } + + $has_fullbox_header = $this->type == 'meta' || $this->type == 'pitm' || + $this->type == 'ipma' || $this->type == 'ispe' || + $this->type == 'pixi' || $this->type == 'iref' || + $this->type == 'auxC'; + if ( $has_fullbox_header ) { + $header_size += 4; + } + if ( $this->size < $header_size ) { + return INVALID; + } + $this->content_size = $this->size - $header_size; + // Avoid timeouts. The maximum number of parsed boxes is arbitrary. + ++$num_parsed_boxes; + if ( $num_parsed_boxes >= MAX_NUM_BOXES ) { + return ABORTED; + } + + $this->version = 0; + $this->flags = 0; + if ( $has_fullbox_header ) { + if ( !( $data = read( $handle, 4 ) ) ) { + return TRUNCATED; + } + $this->version = read_big_endian( $data, 1 ); + $this->flags = read_big_endian( substr( $data, 1, 3 ), 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). + $is_parsable = ( $this->type == 'meta' && $this->version <= 0 ) || + ( $this->type == 'pitm' && $this->version <= 1 ) || + ( $this->type == 'ipma' && $this->version <= 1 ) || + ( $this->type == 'ispe' && $this->version <= 0 ) || + ( $this->type == 'pixi' && $this->version <= 0 ) || + ( $this->type == 'iref' && $this->version <= 1 ) || + ( $this->type == 'auxC' && $this->version <= 0 ); + // Instead of considering this file as invalid, skip unparsable boxes. + if ( !$is_parsable ) { + $this->type = 'unknownversion'; + } + } + // print_r( $this ); // Uncomment to print all boxes. + return FOUND; + } } //------------------------------------------------------------------------------ class Parser { - private $handle; // Input stream. - private $num_parsed_boxes = 0; - private $data_was_skipped = false; - public $features; - - function __construct( $handle ) { - $this->handle = $handle; - $this->features = new Features(); - } - - /** - * Parses an "ipco" box. - * - * "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. - * - * @param stream $handle The resource the box will be parsed from. - * @param int $num_remaining_bytes The number of bytes that should be available from the resource. - * @return Status FOUND on success or an error on failure. - */ - private function parse_ipco( $num_remaining_bytes ) { - $box_index = 1; // 1-based index. Used for iterating over properties. - do { - $box = new Box(); - $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); - if ( $status != FOUND ) { - return $status; - } - - if ( $box->type == 'ispe' ) { - // See ISO/IEC 23008-12:2017(E) 6.5.3.2 - if ( $box->content_size < 8 ) { - return INVALID; - } - if ( ! ( $data = read( $this->handle, 8 ) ) ) { - return TRUNCATED; - } - $width = read_big_endian( substr( $data, 0, 4 ), 4 ); - $height = read_big_endian( substr( $data, 4, 4 ), 4 ); - if ( $width == 0 || $height == 0 ) { - return INVALID; - } - if ( count( $this->features->dim_props ) <= MAX_FEATURES && - $box_index <= MAX_VALUE ) { - $dim_prop_count = count( $this->features->dim_props ); - $this->features->dim_props[ $dim_prop_count ] = new Dim_Prop(); - $this->features->dim_props[ $dim_prop_count ]->property_index = $box_index; - $this->features->dim_props[ $dim_prop_count ]->width = $width; - $this->features->dim_props[ $dim_prop_count ]->height = $height; - } else { - $this->data_was_skipped = true; - } - if ( ! skip( $this->handle, $box->content_size - 8 ) ) { - return TRUNCATED; - } - } elseif ( $box->type == 'pixi' ) { - // See ISO/IEC 23008-12:2017(E) 6.5.6.2 - if ( $box->content_size < 1 ) { - return INVALID; - } - if ( ! ( $data = read( $this->handle, 1 ) ) ) { - return TRUNCATED; - } - $num_channels = read_big_endian( $data, 1 ); - if ( $num_channels < 1 ) { - return INVALID; - } - if ( $box->content_size < 1 + $num_channels ) { - return INVALID; - } - if ( ! ( $data = read( $this->handle, 1 ) ) ) { - return TRUNCATED; - } - $bit_depth = read_big_endian( $data, 1 ); - if ( $bit_depth < 1 ) { - return INVALID; - } - for ( $i = 1; $i < $num_channels; ++$i ) { - if ( ! ( $data = read( $this->handle, 1 ) ) ) { - return TRUNCATED; - } - // Bit depth should be the same for all channels. - if ( read_big_endian( $data, 1 ) != $bit_depth ) { - return INVALID; - } - if ( $i > 32 ) { - return ABORTED; // Be reasonable. - } - } - if ( count( $this->features->chan_props ) <= MAX_FEATURES && - $box_index <= MAX_VALUE && $bit_depth <= MAX_VALUE && - $num_channels <= MAX_VALUE ) { - $chan_prop_count = count( $this->features->chan_props ); - $this->features->chan_props[ $chan_prop_count ] = new Chan_Prop(); - $this->features->chan_props[ $chan_prop_count ]->property_index = $box_index; - $this->features->chan_props[ $chan_prop_count ]->bit_depth = $bit_depth; - $this->features->chan_props[ $chan_prop_count ]->num_channels = $num_channels; - } else { - $this->data_was_skipped = true; - } - if ( ! skip( $this->handle, $box->content_size - ( 1 + $num_channels ) ) ) { - return TRUNCATED; - } - } elseif ( $box->type == 'av1C' ) { - // 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. - if ( $box->content_size < 3 ) { - return INVALID; - } - if ( ! ( $data = read( $this->handle, 3 ) ) ) { - return TRUNCATED; - } - $byte = read_big_endian( substr( $data, 2, 1 ), 1 ); - $high_bitdepth = ( $byte & 0x40 ) != 0; - $twelve_bit = ( $byte & 0x20 ) != 0; - $monochrome = ( $byte & 0x10 ) != 0; - if ( $twelve_bit && ! $high_bitdepth ) { - return INVALID; - } - if ( count( $this->features->chan_props ) <= MAX_FEATURES && - $box_index <= MAX_VALUE ) { - $chan_prop_count = count( $this->features->chan_props ); - $this->features->chan_props[ $chan_prop_count ] = new Chan_Prop(); - $this->features->chan_props[ $chan_prop_count ]->property_index = $box_index; - $this->features->chan_props[ $chan_prop_count ]->bit_depth = - $high_bitdepth ? $twelve_bit ? 12 : 10 : 8; - $this->features->chan_props[ $chan_prop_count ]->num_channels = $monochrome ? 1 : 3; - } else { - $this->data_was_skipped = true; - } - if ( ! skip( $this->handle, $box->content_size - 3 ) ) { - return TRUNCATED; - } - } elseif ( $box->type == 'auxC' ) { - // See AV1 Image File Format (AVIF) 4 - // at https://aomediacodec.github.io/av1-avif/#auxiliary-images - $kAlphaStr = 'urn:mpeg:mpegB:cicp:systems:auxiliary:alpha'; - $kAlphaStrLength = 44; // Includes terminating character. - if ( $box->content_size >= $kAlphaStrLength ) { - if ( ! ( $data = read( $this->handle, $kAlphaStrLength ) ) ) { - return TRUNCATED; - } - if ( substr( $data, 0, $kAlphaStrLength ) == $kAlphaStr ) { - // 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. - $this->features->has_alpha = true; - } - if ( ! skip( $this->handle, $box->content_size - $kAlphaStrLength ) ) { - return TRUNCATED; - } - } else { - if ( ! skip( $this->handle, $box->content_size ) ) { - return TRUNCATED; - } - } - } else { - if ( ! skip( $this->handle, $box->content_size ) ) { - return TRUNCATED; - } - } - ++$box_index; - $num_remaining_bytes -= $box->size; - } while ( $num_remaining_bytes > 0 ); - return NOT_FOUND; - } - - /** - * Parses an "iprp" box. - * - * The "ipco" box contain the properties which are linked to items by the "ipma" box. - * - * @param stream $handle The resource the box will be parsed from. - * @param int $num_remaining_bytes The number of bytes that should be available from the resource. - * @return Status FOUND on success or an error on failure. - */ - private function parse_iprp( $num_remaining_bytes ) { - do { - $box = new Box(); - $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); - if ( $status != FOUND ) { - return $status; - } - - if ( $box->type == 'ipco' ) { - $status = $this->parse_ipco( $box->content_size ); - if ( $status != NOT_FOUND ) { - return $status; - } - } elseif ( $box->type == 'ipma' ) { - // See ISO/IEC 23008-12:2017(E) 9.3.2 - $num_read_bytes = 4; - if ( $box->content_size < $num_read_bytes ) { - return INVALID; - } - if ( ! ( $data = read( $this->handle, $num_read_bytes ) ) ) { - return TRUNCATED; - } - $entry_count = read_big_endian( $data, 4 ); - $id_num_bytes = ( $box->version < 1 ) ? 2 : 4; - $index_num_bytes = ( $box->flags & 1 ) ? 2 : 1; - $essential_bit_mask = ( $box->flags & 1 ) ? 0x8000 : 0x80; - - for ( $entry = 0; $entry < $entry_count; ++$entry ) { - if ( $entry >= MAX_PROPS || - count( $this->features->props ) >= MAX_PROPS ) { - $this->data_was_skipped = true; - break; - } - $num_read_bytes += $id_num_bytes + 1; - if ( $box->content_size < $num_read_bytes ) { - return INVALID; - } - if ( ! ( $data = read( $this->handle, $id_num_bytes + 1 ) ) ) { - return TRUNCATED; - } - $item_id = read_big_endian( - substr( $data, 0, $id_num_bytes ), - $id_num_bytes - ); - $association_count = read_big_endian( - substr( $data, $id_num_bytes, 1 ), - 1 - ); - - for ( $property = 0; $property < $association_count; ++$property ) { - if ( $property >= MAX_PROPS || - count( $this->features->props ) >= MAX_PROPS ) { - $this->data_was_skipped = true; - break; - } - $num_read_bytes += $index_num_bytes; - if ( $box->content_size < $num_read_bytes ) { - return INVALID; - } - if ( ! ( $data = read( $this->handle, $index_num_bytes ) ) ) { - return TRUNCATED; - } - $value = read_big_endian( $data, $index_num_bytes ); - // $essential = ($value & $essential_bit_mask); // Unused. - $property_index = ( $value & ~$essential_bit_mask ); - if ( $property_index <= MAX_VALUE && $item_id <= MAX_VALUE ) { - $prop_count = count( $this->features->props ); - $this->features->props[ $prop_count ] = new Prop(); - $this->features->props[ $prop_count ]->property_index = $property_index; - $this->features->props[ $prop_count ]->item_id = $item_id; - } else { - $this->data_was_skipped = true; - } - } - if ( $property < $association_count ) { - break; // Do not read garbage. - } - } - - // If all features are available now, do not look further. - $status = $this->features->get_primary_item_features(); - if ( $status != NOT_FOUND ) { - return $status; - } - - // Mostly if 'data_was_skipped'. - if ( ! skip( $this->handle, $box->content_size - $num_read_bytes ) ) { - return TRUNCATED; - } - } else { - if ( ! skip( $this->handle, $box->content_size ) ) { - return TRUNCATED; - } - } - $num_remaining_bytes -= $box->size; - } while ( $num_remaining_bytes > 0 ); - return NOT_FOUND; - } - - /** - * Parses an "iref" box. - * - * 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. - * - * @param stream $handle The resource the box will be parsed from. - * @param int $num_remaining_bytes The number of bytes that should be available from the resource. - * @return Status FOUND on success or an error on failure. - */ - private function parse_iref( $num_remaining_bytes ) { - do { - $box = new Box(); - $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); - if ( $status != FOUND ) { - return $status; - } - - if ( $box->type == 'dimg' ) { - // See ISO/IEC 14496-12:2015(E) 8.11.12.2 - $num_bytes_per_id = ( $box->version == 0 ) ? 2 : 4; - $num_read_bytes = $num_bytes_per_id + 2; - if ( $box->content_size < $num_read_bytes ) { - return INVALID; - } - if ( ! ( $data = read( $this->handle, $num_read_bytes ) ) ) { - return TRUNCATED; - } - $from_item_id = read_big_endian( $data, $num_bytes_per_id ); - $reference_count = read_big_endian( substr( $data, $num_bytes_per_id, 2 ), 2 ); - - for ( $i = 0; $i < $reference_count; ++$i ) { - if ( $i >= MAX_TILES ) { - $this->data_was_skipped = true; - break; - } - $num_read_bytes += $num_bytes_per_id; - if ( $box->content_size < $num_read_bytes ) { - return INVALID; - } - if ( ! ( $data = read( $this->handle, $num_bytes_per_id ) ) ) { - return TRUNCATED; - } - $to_item_id = read_big_endian( $data, $num_bytes_per_id ); - $tile_count = count( $this->features->tiles ); - if ( $from_item_id <= MAX_VALUE && $to_item_id <= MAX_VALUE && - $tile_count < MAX_TILES ) { - $this->features->tiles[ $tile_count ] = new Tile(); - $this->features->tiles[ $tile_count ]->tile_item_id = $to_item_id; - $this->features->tiles[ $tile_count ]->parent_item_id = $from_item_id; - } else { - $this->data_was_skipped = true; - } - } - - // If all features are available now, do not look further. - $status = $this->features->get_primary_item_features(); - if ( $status != NOT_FOUND ) { - return $status; - } - - // Mostly if 'data_was_skipped'. - if ( ! skip( $this->handle, $box->content_size - $num_read_bytes ) ) { - return TRUNCATED; - } - } else { - if ( ! skip( $this->handle, $box->content_size ) ) { - return TRUNCATED; - } - } - $num_remaining_bytes -= $box->size; - } while ( $num_remaining_bytes > 0 ); - return NOT_FOUND; - } - - /** - * Parses a "meta" box. - * - * It looks for the primary item ID in the "pitm" box and recurses into other boxes - * to find its features. - * - * @param stream $handle The resource the box will be parsed from. - * @param int $num_remaining_bytes The number of bytes that should be available from the resource. - * @return Status FOUND on success or an error on failure. - */ - private function parse_meta( $num_remaining_bytes ) { - do { - $box = new Box(); - $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); - if ( $status != FOUND ) { - return $status; - } - - if ( $box->type == 'pitm' ) { - // See ISO/IEC 14496-12:2015(E) 8.11.4.2 - $num_bytes_per_id = ( $box->version == 0 ) ? 2 : 4; - if ( $num_bytes_per_id > $num_remaining_bytes ) { - return INVALID; - } - if ( ! ( $data = read( $this->handle, $num_bytes_per_id ) ) ) { - return TRUNCATED; - } - $primary_item_id = read_big_endian( $data, $num_bytes_per_id ); - if ( $primary_item_id > MAX_VALUE ) { - return ABORTED; - } - $this->features->has_primary_item = true; - $this->features->primary_item_id = $primary_item_id; - if ( ! skip( $this->handle, $box->content_size - $num_bytes_per_id ) ) { - return TRUNCATED; - } - } elseif ( $box->type == 'iprp' ) { - $status = $this->parse_iprp( $box->content_size ); - if ( $status != NOT_FOUND ) { - return $status; - } - } elseif ( $box->type == 'iref' ) { - $status = $this->parse_iref( $box->content_size ); - if ( $status != NOT_FOUND ) { - return $status; - } - } else { - if ( ! skip( $this->handle, $box->content_size ) ) { - return TRUNCATED; - } - } - $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". - return INVALID; - } - - /** - * Parses a file stream. - * - * The file type is checked through the "ftyp" box. - * - * @return bool True if the input stream is an AVIF bitstream or false. - */ - public function parse_ftyp() { - $box = new Box(); - $status = $box->parse( $this->handle, $this->num_parsed_boxes ); - if ( $status != FOUND ) { - return false; - } - - if ( $box->type != 'ftyp' ) { - return false; - } - // Iterate over brands. See ISO/IEC 14496-12:2012(E) 4.3.1 - if ( $box->content_size < 8 ) { - return false; - } - for ( $i = 0; $i + 4 <= $box->content_size; $i += 4 ) { - if ( ! ( $data = read( $this->handle, 4 ) ) ) { - return false; - } - if ( $i == 4 ) { - continue; // Skip minor_version. - } - if ( substr( $data, 0, 4 ) == 'avif' || substr( $data, 0, 4 ) == 'avis' ) { - return skip( $this->handle, $box->content_size - ( $i + 4 ) ); - } - if ( $i > 32 * 4 ) { - return false; // Be reasonable. - } - } - return false; // No AVIF brand no good. - } - - /** - * Parses a file stream. - * - * Features are extracted from the "meta" box. - * - * @return bool True if the main features of the primary item were parsed or false. - */ - public function parse_file() { - $box = new Box(); - while ( $box->parse( $this->handle, $this->num_parsed_boxes ) == FOUND ) { - if ( $box->type === 'meta' ) { - if ( $this->parse_meta( $box->content_size ) != FOUND ) { - return false; - } - return true; - } - if ( ! skip( $this->handle, $box->content_size ) ) { - return false; - } - } - return false; // No "meta" no good. - } + private $handle; // Input stream. + private $num_parsed_boxes = 0; + private $data_was_skipped = false; + public $features; + + function __construct( $handle ) { + $this->handle = $handle; + $this->features = new Features(); + } + + /** + * Parses an "ipco" box. + * + * "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. + * + * @param stream $handle The resource the box will be parsed from. + * @param int $num_remaining_bytes The number of bytes that should be available from the resource. + * @return Status FOUND on success or an error on failure. + */ + private function parse_ipco( $num_remaining_bytes ) { + $box_index = 1; // 1-based index. Used for iterating over properties. + do { + $box = new Box(); + $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); + if ( $status != FOUND ) { + return $status; + } + + if ( $box->type == 'ispe' ) { + // See ISO/IEC 23008-12:2017(E) 6.5.3.2 + if ( $box->content_size < 8 ) { + return INVALID; + } + if ( !( $data = read( $this->handle, 8 ) ) ) { + return TRUNCATED; + } + $width = read_big_endian( substr( $data, 0, 4 ), 4 ); + $height = read_big_endian( substr( $data, 4, 4 ), 4 ); + if ( $width == 0 || $height == 0 ) { + return INVALID; + } + if ( count( $this->features->dim_props ) <= MAX_FEATURES && + $box_index <= MAX_VALUE ) { + $dim_prop_count = count( $this->features->dim_props ); + $this->features->dim_props[$dim_prop_count] = new Dim_Prop(); + $this->features->dim_props[$dim_prop_count]->property_index = $box_index; + $this->features->dim_props[$dim_prop_count]->width = $width; + $this->features->dim_props[$dim_prop_count]->height = $height; + } else { + $this->data_was_skipped = true; + } + if ( !skip( $this->handle, $box->content_size - 8 ) ) { + return TRUNCATED; + } + } else if ( $box->type == 'pixi' ) { + // See ISO/IEC 23008-12:2017(E) 6.5.6.2 + if ( $box->content_size < 1 ) { + return INVALID; + } + if ( !( $data = read( $this->handle, 1 ) ) ) { + return TRUNCATED; + } + $num_channels = read_big_endian( $data, 1 ); + if ( $num_channels < 1 ) { + return INVALID; + } + if ( $box->content_size < 1 + $num_channels ) { + return INVALID; + } + if ( !( $data = read( $this->handle, 1 ) ) ) { + return TRUNCATED; + } + $bit_depth = read_big_endian( $data, 1 ); + if ( $bit_depth < 1 ) { + return INVALID; + } + for ( $i = 1; $i < $num_channels; ++$i ) { + if ( !( $data = read( $this->handle, 1 ) ) ) { + return TRUNCATED; + } + // Bit depth should be the same for all channels. + if ( read_big_endian( $data, 1 ) != $bit_depth ) { + return INVALID; + } + if ( $i > 32 ) { + return ABORTED; // Be reasonable. + } + } + if ( count( $this->features->chan_props ) <= MAX_FEATURES && + $box_index <= MAX_VALUE && $bit_depth <= MAX_VALUE && + $num_channels <= MAX_VALUE ) { + $chan_prop_count = count( $this->features->chan_props ); + $this->features->chan_props[$chan_prop_count] = new Chan_Prop(); + $this->features->chan_props[$chan_prop_count]->property_index = $box_index; + $this->features->chan_props[$chan_prop_count]->bit_depth = $bit_depth; + $this->features->chan_props[$chan_prop_count]->num_channels = $num_channels; + } else { + $this->data_was_skipped = true; + } + if ( !skip( $this->handle, $box->content_size - ( 1 + $num_channels ) ) ) { + return TRUNCATED; + } + } else if ( $box->type == 'av1C' ) { + // 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. + if ( $box->content_size < 3 ) { + return INVALID; + } + if ( !( $data = read( $this->handle, 3 ) ) ) { + return TRUNCATED; + } + $byte = read_big_endian( substr( $data, 2, 1 ), 1 ); + $high_bitdepth = ( $byte & 0x40 ) != 0; + $twelve_bit = ( $byte & 0x20 ) != 0; + $monochrome = ( $byte & 0x10 ) != 0; + if ( $twelve_bit && !$high_bitdepth ) { + return INVALID; + } + if ( count( $this->features->chan_props ) <= MAX_FEATURES && + $box_index <= MAX_VALUE ) { + $chan_prop_count = count( $this->features->chan_props ); + $this->features->chan_props[$chan_prop_count] = new Chan_Prop(); + $this->features->chan_props[$chan_prop_count]->property_index = $box_index; + $this->features->chan_props[$chan_prop_count]->bit_depth = + $high_bitdepth ? $twelve_bit ? 12 : 10 : 8; + $this->features->chan_props[$chan_prop_count]->num_channels = $monochrome ? 1 : 3; + } else { + $this->data_was_skipped = true; + } + if ( !skip( $this->handle, $box->content_size - 3 ) ) { + return TRUNCATED; + } + } else if ( $box->type == 'auxC' ) { + // See AV1 Image File Format (AVIF) 4 + // at https://aomediacodec.github.io/av1-avif/#auxiliary-images + $kAlphaStr = "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"; + $kAlphaStrLength = 44; // Includes terminating character. + if ( $box->content_size >= $kAlphaStrLength ) { + if ( !( $data = read( $this->handle, $kAlphaStrLength ) ) ) { + return TRUNCATED; + } + if ( substr( $data, 0, $kAlphaStrLength ) == $kAlphaStr ) { + // 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. + $this->features->has_alpha = true; + } + if ( !skip( $this->handle, $box->content_size - $kAlphaStrLength ) ) { + return TRUNCATED; + } + } else { + if ( !skip( $this->handle, $box->content_size ) ) { + return TRUNCATED; + } + } + } else { + if ( !skip( $this->handle, $box->content_size ) ) { + return TRUNCATED; + } + } + ++$box_index; + $num_remaining_bytes -= $box->size; + } while ( $num_remaining_bytes > 0 ); + return NOT_FOUND; + } + + /** + * Parses an "iprp" box. + * + * The "ipco" box contain the properties which are linked to items by the "ipma" box. + * + * @param stream $handle The resource the box will be parsed from. + * @param int $num_remaining_bytes The number of bytes that should be available from the resource. + * @return Status FOUND on success or an error on failure. + */ + private function parse_iprp( $num_remaining_bytes ) { + do { + $box = new Box(); + $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); + if ( $status != FOUND ) { + return $status; + } + + if ( $box->type == 'ipco' ) { + $status = $this->parse_ipco( $box->content_size ); + if ( $status != NOT_FOUND ) { + return $status; + } + } else if ( $box->type == 'ipma' ) { + // See ISO/IEC 23008-12:2017(E) 9.3.2 + $num_read_bytes = 4; + if ( $box->content_size < $num_read_bytes ) { + return INVALID; + } + if ( !( $data = read( $this->handle, $num_read_bytes ) ) ) { + return TRUNCATED; + } + $entry_count = read_big_endian( $data, 4 ); + $id_num_bytes = ( $box->version < 1 ) ? 2 : 4; + $index_num_bytes = ( $box->flags & 1 ) ? 2 : 1; + $essential_bit_mask = ( $box->flags & 1 ) ? 0x8000 : 0x80; + + for ( $entry = 0; $entry < $entry_count; ++$entry ) { + if ( $entry >= MAX_PROPS || + count( $this->features->props ) >= MAX_PROPS ) { + $this->data_was_skipped = true; + break; + } + $num_read_bytes += $id_num_bytes + 1; + if ( $box->content_size < $num_read_bytes ) { + return INVALID; + } + if ( !( $data = read( $this->handle, $id_num_bytes + 1 ) ) ) { + return TRUNCATED; + } + $item_id = read_big_endian( + substr( $data, 0, $id_num_bytes ), $id_num_bytes ); + $association_count = read_big_endian( + substr( $data, $id_num_bytes, 1 ), 1 ); + + for ( $property = 0; $property < $association_count; ++$property ) { + if ( $property >= MAX_PROPS || + count( $this->features->props ) >= MAX_PROPS ) { + $this->data_was_skipped = true; + break; + } + $num_read_bytes += $index_num_bytes; + if ( $box->content_size < $num_read_bytes ) { + return INVALID; + } + if ( !( $data = read( $this->handle, $index_num_bytes ) ) ) { + return TRUNCATED; + } + $value = read_big_endian( $data, $index_num_bytes ); + // $essential = ($value & $essential_bit_mask); // Unused. + $property_index = ( $value & ~$essential_bit_mask ); + if ( $property_index <= MAX_VALUE && $item_id <= MAX_VALUE ) { + $prop_count = count( $this->features->props ); + $this->features->props[$prop_count] = new Prop(); + $this->features->props[$prop_count]->property_index = $property_index; + $this->features->props[$prop_count]->item_id = $item_id; + } else { + $this->data_was_skipped = true; + } + } + if ( $property < $association_count ) { + break; // Do not read garbage. + } + } + + // If all features are available now, do not look further. + $status = $this->features->get_primary_item_features(); + if ( $status != NOT_FOUND ) { + return $status; + } + + // Mostly if 'data_was_skipped'. + if ( !skip( $this->handle, $box->content_size - $num_read_bytes ) ) { + return TRUNCATED; + } + } else { + if ( !skip( $this->handle, $box->content_size ) ) { + return TRUNCATED; + } + } + $num_remaining_bytes -= $box->size; + } while ( $num_remaining_bytes > 0 ); + return NOT_FOUND; + } + + /** + * Parses an "iref" box. + * + * 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. + * + * @param stream $handle The resource the box will be parsed from. + * @param int $num_remaining_bytes The number of bytes that should be available from the resource. + * @return Status FOUND on success or an error on failure. + */ + private function parse_iref( $num_remaining_bytes ) { + do { + $box = new Box(); + $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); + if ( $status != FOUND ) { + return $status; + } + + if ( $box->type == 'dimg' ) { + // See ISO/IEC 14496-12:2015(E) 8.11.12.2 + $num_bytes_per_id = ( $box->version == 0 ) ? 2 : 4; + $num_read_bytes = $num_bytes_per_id + 2; + if ( $box->content_size < $num_read_bytes ) { + return INVALID; + } + if ( !( $data = read( $this->handle, $num_read_bytes ) ) ) { + return TRUNCATED; + } + $from_item_id = read_big_endian( $data, $num_bytes_per_id ); + $reference_count = read_big_endian( substr( $data, $num_bytes_per_id, 2 ), 2 ); + + for ( $i = 0; $i < $reference_count; ++$i ) { + if ( $i >= MAX_TILES ) { + $this->data_was_skipped = true; + break; + } + $num_read_bytes += $num_bytes_per_id; + if ( $box->content_size < $num_read_bytes ) { + return INVALID; + } + if ( !( $data = read( $this->handle, $num_bytes_per_id ) ) ) { + return TRUNCATED; + } + $to_item_id = read_big_endian( $data, $num_bytes_per_id ); + $tile_count = count( $this->features->tiles ); + if ( $from_item_id <= MAX_VALUE && $to_item_id <= MAX_VALUE && + $tile_count < MAX_TILES ) { + $this->features->tiles[$tile_count] = new Tile(); + $this->features->tiles[$tile_count]->tile_item_id = $to_item_id; + $this->features->tiles[$tile_count]->parent_item_id = $from_item_id; + } else { + $this->data_was_skipped = true; + } + } + + // If all features are available now, do not look further. + $status = $this->features->get_primary_item_features(); + if ( $status != NOT_FOUND ) { + return $status; + } + + // Mostly if 'data_was_skipped'. + if ( !skip( $this->handle, $box->content_size - $num_read_bytes ) ) { + return TRUNCATED; + } + } else { + if ( !skip( $this->handle, $box->content_size ) ) { + return TRUNCATED; + } + } + $num_remaining_bytes -= $box->size; + } while ( $num_remaining_bytes > 0 ); + return NOT_FOUND; + } + + /** + * Parses a "meta" box. + * + * It looks for the primary item ID in the "pitm" box and recurses into other boxes + * to find its features. + * + * @param stream $handle The resource the box will be parsed from. + * @param int $num_remaining_bytes The number of bytes that should be available from the resource. + * @return Status FOUND on success or an error on failure. + */ + private function parse_meta( $num_remaining_bytes ) { + do { + $box = new Box(); + $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); + if ( $status != FOUND ) { + return $status; + } + + if ( $box->type == 'pitm' ) { + // See ISO/IEC 14496-12:2015(E) 8.11.4.2 + $num_bytes_per_id = ( $box->version == 0 ) ? 2 : 4; + if ( $num_bytes_per_id > $num_remaining_bytes ) { + return INVALID; + } + if ( !( $data = read( $this->handle, $num_bytes_per_id ) ) ) { + return TRUNCATED; + } + $primary_item_id = read_big_endian( $data, $num_bytes_per_id ); + if ( $primary_item_id > MAX_VALUE ) { + return ABORTED; + } + $this->features->has_primary_item = true; + $this->features->primary_item_id = $primary_item_id; + if ( !skip( $this->handle, $box->content_size - $num_bytes_per_id ) ) { + return TRUNCATED; + } + } else if ( $box->type == 'iprp' ) { + $status = $this->parse_iprp( $box->content_size ); + if ( $status != NOT_FOUND ) { + return $status; + } + } else if ( $box->type == 'iref' ) { + $status = $this->parse_iref( $box->content_size ); + if ( $status != NOT_FOUND ) { + return $status; + } + } else { + if ( !skip( $this->handle, $box->content_size ) ) { + return TRUNCATED; + } + } + $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". + return INVALID; + } + + /** + * Parses a file stream. + * + * The file type is checked through the "ftyp" box. + * + * @return bool True if the input stream is an AVIF bitstream or false. + */ + public function parse_ftyp() { + $box = new Box(); + $status = $box->parse( $this->handle, $this->num_parsed_boxes ); + if ( $status != FOUND ) { + return false; + } + + if ( $box->type != 'ftyp' ) { + return false; + } + // Iterate over brands. See ISO/IEC 14496-12:2012(E) 4.3.1 + if ( $box->content_size < 8 ) { + return false; + } + for ( $i = 0; $i + 4 <= $box->content_size; $i += 4 ) { + if ( !( $data = read( $this->handle, 4 ) ) ) { + return false; + } + if ( $i == 4 ) { + continue; // Skip minor_version. + } + if ( substr( $data, 0, 4 ) == 'avif' || substr( $data, 0, 4 ) == 'avis' ) { + return skip( $this->handle, $box->content_size - ( $i + 4 ) ); + } + if ( $i > 32 * 4 ) { + return false; // Be reasonable. + } + + } + return false; // No AVIF brand no good. + } + + /** + * Parses a file stream. + * + * Features are extracted from the "meta" box. + * + * @return bool True if the main features of the primary item were parsed or false. + */ + public function parse_file() { + $box = new Box(); + while ( $box->parse( $this->handle, $this->num_parsed_boxes ) == FOUND ) { + if ( $box->type === 'meta' ) { + if ( $this->parse_meta( $box->content_size ) != FOUND ) { + return false; + } + return true; + } + if ( !skip( $this->handle, $box->content_size ) ) { + return false; + } + } + return false; // No "meta" no good. + } } From 897ffb56557f01b6650583ee29e92d30bd41c678 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Mon, 13 Nov 2023 12:39:36 -0700 Subject: [PATCH 11/79] remove excessive whitespace --- src/wp-includes/media.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 6bd1f44182054..025e4276db61a 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -5627,7 +5627,6 @@ function wp_get_avif_info( $filename ) { return $features; } - } /** From d3fc325c9ca26631882a7ea2060a673abe46b5e0 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 12 Jan 2024 08:44:39 -0700 Subject: [PATCH 12/79] Add AVIF test images --- .../phpunit/data/images/avif-test-animated.avif | Bin 0 -> 24483 bytes .../phpunit/data/images/avif-test-lossless.avif | Bin 0 -> 28490 bytes tests/phpunit/data/images/avif-test-lossy.avif | Bin 0 -> 13513 bytes .../phpunit/data/images/avif-test-rotated.avif | Bin 0 -> 84837 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/phpunit/data/images/avif-test-animated.avif create mode 100644 tests/phpunit/data/images/avif-test-lossless.avif create mode 100644 tests/phpunit/data/images/avif-test-lossy.avif create mode 100644 tests/phpunit/data/images/avif-test-rotated.avif diff --git a/tests/phpunit/data/images/avif-test-animated.avif b/tests/phpunit/data/images/avif-test-animated.avif new file mode 100644 index 0000000000000000000000000000000000000000..6d6a34a7305f8153679662267fa4ee1f64e98ce9 GIT binary patch literal 24483 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 literal 0 HcmV?d00001 diff --git a/tests/phpunit/data/images/avif-test-lossless.avif b/tests/phpunit/data/images/avif-test-lossless.avif new file mode 100644 index 0000000000000000000000000000000000000000..7eb2d5ce68ee6a413cc879f1ad1a0a289fadee4c GIT binary patch literal 28490 zcmXteW0)pQ&-UON+dH?xXVP8ySJmkT0s{67X1 z5)$&Cfz@|m6!a(jj{!n4w6$`uF|=|60{JIEe*Y*8b31GO|6>2}uP^{GNFd-}RqWPA z`cB9|&`|6KjIYnL1O!mOb2ovdfPl6YFKRvUm{%n~w?$h!F*4u3L?2WiMK8%2%Pg37 z8ZgG9KTq3XEezg`qjo&LsjZUqmmeM9+l5Ow(D~_HxI_P3AM#4t9LTrVbGRoGG94s) z;@_n#H$J%kfa4+n4W?%6CUCOtLA{WHw&@9{WM}T3Hv|L~<~PT}vvH`u9DkHZN2C{n zNZ$%64+-Bhaf^YoRDS7Gn{|{wRk(*aUI3VXX_tXRAULJEyGsTzC;P4(M-kC_fJVJx+s!$y-W_@CSBg+H zLh&+{A%WKilf9oS`~lz+NO^#C2(Fo=Vv%TPF9>)|7zoN*=9s#on9Y$NsU^O@l(he8 zi^}lp(lQaM8hczsRsr*Lv}Le}h_A+*y;tsQ>!8@lmBJM)PxgjdfW=PvTrI*KEtYQT z!Uj*p$-~(2s=ZUUKzz~cW>AHV%o+(M`<3UN&cxrn7`9I9pyX;+)k37_7w|QzFdijJ}-n`@14qAk#D}>*qPClNpw`kqL zeMW)Ml;7uDss6j^yDFg$bEufzF&HSz{bD}o)zK@LeC*yUH>^7A8}9}pQs_h zF3CkvfqC~NIWP~QMq%f=J|jw@hd2%NUWR16TQM{bM)6r%&$|P> zm(yF5iBPWLzsWn_4$LNs}&L&T65unHjVRMEcBEv%^=jqk|}BLas+|p-i9gN8-TalZ+j)`nL5k zuWWjy#Kqm^heQpaUMb2&jqL4**>M=&>T_i!%qfZrXLiJp%Q6jY#QRJAD2kwi+zfLITZa@2KIWY%i@EC7^d_-=}|duxZIAUbPsSX%0CD)-M<`x<`Rq@s>@_ zO^)>I(Dw3DWEVI-wRx%?n&u!vR_3iC$n&5gkrw~_WchE6CJ`MazwRZU+oX1-q{|`7 z#He;nr*a-3+d1$nEg0=fSluFN}x+ z9SO5!gk+AoKyW=~JZ}WA@V>Mm9ag#o6lm#&D4etWk_1KpEGgecUccntV^?EZAv1tgPz$#O_vk}Y%rhR`{OtHzvM8vEYd(c)liDs` zg=#)=$t*NoaXHRL;IkeW2idSKc>h!OF6t*GXQeAAmf2)5xfKD$1u(g!$1Zxw`NzRW z`5BPwFZ!ArMo`3mYIM1CM35Aq0QWMBwg%CFCesZkFz#|V#8v7No&&L zSr=A1$&r-l4}~tJol_fxiFbsx8_rTTwKm3zHdmZfMl*-cGQBe~H{E!k*`EjrO@4YN{E zUFL`@=^_X8)=04#TiJllmC-1U8eI-&{-28D=hw$3!wYd>?fqt46c(n}T0M?5(wlOy1!I?=xA~1P(lIX3=>77z>^|u0`^Qs!Y48C zY#&|^nj+?I!^)m2Q=nq-q}%ckzF1$Pqto(M>#d9jJLLK-zCoF-v0&69nypD0cI>`Q zoW9R~=ljU~Pgp?m_+ed?PGl5?Oso$7L`^G?iiH=j9A*&3F&)G)9R>sW4$y_*6@}s{ zc+Mbw=eeuN_Ag1-NdfN?nWv5*;Rv9fbv%7k?dp5GPX68r&Q_1+vScgAV(wl5n*4oA zlGW~TvNO1xT({Hc5}y446%%{-XD7&M4EC<&<5o=(Qh9hr50-$@BI`1p?v8}V=|XF> zOe-&K!;B-A`S;2CPe`+c9?q=yc%7b*HJ7oUrc+}NW&YFOG!dvm#WOOC5~prT00v=iENGY{1GBSamJ)Q)Z@;3pH*N<0Fx8T=$qTVvj`suU*6xM29gBfX|w?8d( zUY)gN&7`Olq5v*?WYgq~4X7~xzxJgYmfp8ooX#^Xn?(I`O(ZeCl~;C#IN{i5LmH0N zH)N$XENKhCDxMgz4IgSPfh3h=lVlr=-!gnal#=gM^Kk&6-MQ{D= zaGv{1$Hrd;LJ@?VD_p3*Von=RAp`w&c z_v=8L9|Ju)-S#FR{=1Q%$2nvS9=x7+y2(Q*ISa)2n!D36JWBdMKsqkp;qkFaJpF9T z;Skc4t+htwb%yFe5w*Yp?^di((M-pZ8`V2&k}aiK=gX~VMX9(Z3y9VpwQ;*0LnkYh zaS%R5eo}1N(HzkQ!XY1C8gi9;>4H~M9IYGkVCkIoMUou?H|pWgU(L}D*S9SJpyj8Q zE16I#`lknbe{gjLDJSXZ7xf76IGeAkcZNO}kQsGi;Ixmd%seKPDM6NuYhSdG?6)gh63LKmkO70SG= z5b+86pCd|p0eh`$HkhXmN1xs)u6ovn`bQE}RQ7`|OsvMin61HAy5jr{Lx!7ky?r_U znQgU2{mecZ0PBXTUi&P@7@pqay_qT9N-89^I&|*uEBQ8K+tcgHOS=)R0QR;A`**e> zSO1y$VaD;K4uiaMZz>qcR0#K{pMq8s*QfD#69~S|w6ZA*pVf5Y1J8;=Bxm;&?GkN@ zp}c%Vk{;nlH=DQXL7<7e^+#bhUS`v%`Yc|{Zq#F|6$bSv=4u4)ZuOfES7R`Mt`>dR z-kC99d3F)Cv01}GuZIBLJS%Si3y2IoQhbL95yR^?oR)dSm3? zEQiZUNBarLzIhcQaujkhz-!Yap-H)*Nm6CVZ4jMl>A*k>SaH&HLCJlZzuVK<(ghW37tqJTIBY+X!&B&x#RRrL;4Fl z8kQk8hr>>;mO6qlmawF4IoO&bazqu^3H|c*Z=in+czJHYj116)V6|bJyJM6bFdwIY zm)kDLpKp(M-V)n!W99y2v4zR4c;`(#-B)+GuFq8c+e9k`PmdfodK5rfo(FKzZuAE& zLFD)Yr8xgqzGiJ3A&LvpW17bCP9-(#0<;|dC|42cL$~+`^-1*LHetp+gYei^W3bnw z&1pB@ufxjN+V;T!Et?jiC-mjGUqV?r!&pg0a@j7<%^3y<3JLSqf9alL10b6H*dg&j z5{R%8Kdycahe8xbjVkiX!U{fM-c`}mNYQAu(oQ&%cbVE)Q`8@?9=Y?$V=~S&tq(Ph zW~i;Lb2rqx$S+l^n%Q*iIV&ME+9t0AFsLU7p(^`b{o6a61uMcV=W0cIm>iyXlDG|X zqKt`GM2Eg#Lq$npCB~d2eeBQ!D9{`Z-n+d+m@%}JhC1tSeu^O7pj1#OazM%N=H9w> zUDv7nTKJ6OfaEmO>g8-kqyP`gjcD^+`K4+jEEza(iMde53AjPdr@y+FZMzy`YQo9bJ3h3g>*WeZFWSn@+M22kH*;k(a>%kh|x6YyGqP z5`XqWyl4&C?^KnVoE!QV`d+eU-Q-g@O!xz6J*6T`1?(i}dXyiI`-%^iJR>uDQM=`H|Zd z>jLhG4$MVHXa;+KfUWhr;i`&9dB`{52(XuMv4;X~k7Z}^KMEtFg=hMENb}K9B}4}W zTBop2gb&UtUGGn4iWUo%wWL%IH-s-&(ZTlPbel~(D{$XMYNsIdgycmceapXWI>Hj< zybDane_b2F!nSAT`sVhg*7Wi=8h-Uzd-XAfb=F+>K5$Qt;n)7@3jAW1J9aD?Da1=l zP!-H*w>V)dleGx(@v-FeKkk(m2Xw20rdi>5h-LY_uKxXQ2#Bz{U}to-cbDzV=i*jQc^5n<^28346LpPoQ46k&wgL$jF5?@g_N^abRy}iP3pAzR83&}lAACh&t&c7*b&-ZNs9yTGP7?8YuOFqZ;S&&&Wf#dp3|c znuJIxaMz%rcHP&&FKpVqwCX@6uIx~O7{Ugr-C2LW(+d$;bc3n7=2je zFkEw1QRjq_u$NL4q%*^%$8y^ZHUtmp*Ox;ifoA!w6V>O(8FR z11atUm}{3WWrjuHO-}h&y+*8hw( zGyr)S&G;DZabY7mG*&Z(nlbI`4aKA&z1OEMX$@aP#X zY-Hhc9GFxi@YxRdqxJVlXi;$5J}96b8q&`d7U=M z(J3)D$-HopoAY+8gboqX@6TTm+^nK%xf^a*+6 zx~?t%U}NpqxeX4|v9Z#H^~XYQZ!bqAV`iHe7@&E!3qe%pvHe*&aR>9tt@SVA*-*E;k%{}dJR}`TnF7HA(3CV(!9`jT!Si&~wBcM6H~q+lrL z&aOn^-ieXr8P{f_`%I4uF9cJ8*Dcm($cT#{+v7Z(b`0c8$Q0yoc1oE zytCtKJxKV1qms-bM-c5pJX>TdY@WGxsfHH{ucpI_Tc)%;i1; zb*njcLr9^H&O=6zL$nJ%@n3c_SMiA*sn?M+N|b&Li`WBE_14cT#KX@31!;BtY_wJ< z#_r~UL5<5f8YX1@-RYwoNQV>KT^&Jzr&S*a_V|!WHGK-J!4f4f9Jgvi8A8}-?w{^> zfvdSQSj9W;my28u#r#t#;_3IDMBq}_+C~oM*cg1;d>hepMkt@>G>7$`X1`{z=%&_t0y<9~;X- zVL6}NrMRC_enS4fN_31_IEvL`*@s=yFx5yhH>+`>NU~EXHv(ktvNk*Xt{(;e1X@wQ z3RNOe5x~4XU$EU_5L2gSKIl z``6mWbJSnrCH0iNxuELmS?ydBFT8#63AcMQwdpdU`TM8Wqq8sDMOt08%$?U;U10s~ zFrLMxCeGT3F7?BOE$XGF*CS5*JR?m%FC%%;!5#;yb({hGbJOgrG%b&vVrlx>nimGB zp}D$#?4!k$QefQfT$KycJ5-~+NNcbP(I&2fBBfJ8X2LFa7}Q~S{=@u|*apYtD@!dK zQC5ms*-kz2l%-YKy0_Jj^}wVw^crnb5mHW#Sih~aI_i}WvH0PWdx+R;eH=OZasOL3 zaakC|ytweQ2-P$<{o`|_KN)i6w7jwzjbgvHJTV|*|EPHQ5A011h^uzLm8FfCGME zR1{6c52^yROS77nq^HOz83!+8=avX7tyvcDX>)`)CoQw&jBxII*sQX_nhnmDV!*`n zAG-a};TQ0=m$k>qPOawi2=27w$O|!MbddwnKw3xuNF3}N9I5W#OE?8zJT+t3zr%`u zKNrLKwTJ6_OAJy%3L7Pys0b5e3dNX}8LaR35~swpF9!C^=iyzI)iLhv z(KxVQ60^A*GRNedZIJzRBM4f&I>74*&CXD;^V23g^lroPrIOXmwd*Untc3YDq`_Q2 z%fGJLLcgm2-9D~@IJRFny9|vrRn@_Khf;7aJ?=EpQzZ~kYbLi!n7c#}(8fMJZlzsa z+g5-?n(yN8&PMhiYp&|pt7l2rPfa|9SAWY&sTg|djV)2_fHqv&T`vGd z{($wtrgo#DKRKB=hv+zam=knd=c>KB=wF;`Ml$=~e((;mlVX z$<%(2!vY3YxZUpygW1KzoynTp?r4N91oGsv4~lt3rtM(77AJF6LRzLgLHzfuAG_JA|}Rfp*c0HlnY7^ zzjeL8as7*9u8=>rYh~b!6ux7ud}N6NW0AVh9z{C;IuuwDM+sC%cgB0=>oO#{Wgc>p zD}(;D;g*fxZ3%jQq9#G6lm+KhtOl)6rsAU!2EB0OvE4&>yX>tV8$Jf~B99Rh4a;tY zar`*hdC|4lst9ygocYVA$MYtbz_yVN;XNZvUEC47!B*QFj4z3cCd?yi0Dzfvv_FO? zwdv((Ul)A&9;5A5Nz!TBI(wEI)4>i=xFobb1H6Ktw#R>)hvs`M^ha^Z`+%5w#xx++5$vVJv_i&4V-JqG_Bk15Ar z(4v1sev~n2z7LT4>%rixtT^KAA+eqO^x!H7q2B#K&G5dlO*u5qB6nI7)FxDBkALt@ z3c!#ed4h;+C*!eEdGJLwVL#V~nzd|7eWI!-?yWN>j(!&bUTqM)iyZe_ZE=E}g~ynZ&8D=dBzPec4N>jE;PCv2E|`SK5M;nT)t(MGi(&*AWI& zB%>?~*dM5(J@HO$Jg*hRCW0>)c+$rd>TGu*OH^u0(KoMo>EU_x&4h|@uzj|q@hs!2 z8MWipAT+?qulY*-4YiEMym|j6A)c+{c=(AxmoUA#^$u$?c6Xa84W&J7pZR(fOk1N^CoYRGmA3Or-^zN#9 z(8&3w)TLFTu*IxYmZ@K{(WGKlJ64}{p&DTmn&S|KSb>!P)-Pl1=K z>@?G+D%qf_P?j!RlSCZwdYo7?VPUX1B_7aKuuFy9wZLll=9ojR#t$Ov@OHAF7rog} z;@e74SIy7)4=GTFlYRbq6ijd>KD|%hb->A%g%1gvAgqDLQtUlrxm9DHY zro;-i<8*qY3@V(k4nRz`7|7Kj0$Gj?S(olHxTq%jGmJH9Z&!vly>VJo8qZLeUZsNA z&x`X>!smbwE(mgHo99=PIa&;+&9wEH%yA@bePr6chl$>f0I_g_K%f0hKdX&0gJME2 z-QF2*CRr|qCRHeNd9p%%&J}uzZ}+`64P&}@${I;U3elJ|6O80~{P!Rzpl!Ag#T~^k zR5i2PYmK*?I{SmE^_ccb5U3iMfq-p{Cz#u{h-?=Yz>GZZ#ji3qztC0Makztb9fa@- z&vsKFK!|VyjYdCs;_^NtS+Vkzi@#|Qc!>#%+x@z$ZJOE|#c>Gzx4tk@@VlJebL;p8 z+Ob}vQEPteN$K=08=EyMmV>F}Ythv~<*)VGO3GrjJ2ai^9)>Xp{kfeshdBuNsIz`9;;aYl|rU_@0o9 z-MKgDE2M6QR}CV`5SIBB0FV%jEkeXQAiryY^UOhYf$tnSAucr@cf5<3Q_dR}bIDM0 zxfrPODydlO%d&C3#}~nEyk>O-kyIKBHQB39YqZ%L%?y~RnSouUWLPJ1Z$?B za{CGxpJHt^=Wr+`JoznU#7B=p$w=EK#4)`+7Ss^X)i|b+Ts3R4uIDpK>9r7UwGYAj zCNc)PCo2fM>w`skgKCieO8Rh%YSMzjm24z~`$77Js1O7s@I`e9s>=Jp2O|)=Cv^N< z>K&l_b3gXGV($h2ahIgW#6fPdnOI%0eA9Ns_K=uDQqYJ0NGZowLPkc~95N`z$H|J> zt!f>nm5l_7=$$Itv1rbnUkdsD^SwTF--q($$|I)=kn*1i|A-*_ zgR*nZ^WcUyGMEW~+wNJHp7G@G*z8i&gjYgRLuir^LGUz3_?LmnD|e zb~ru~MM{KU53Tb$#vMNB%%ZqU-~uzT8!AGupu7#_uv97}_7AlDsjx*6G&JGoG=y>d z>bL2T;pa3DPFjEC(jfG#i#pA2csexJ&{AH&I?D=#6l(|&h7ptabehrOL?iqZr{kf z>0)F!YUn+z$-8iSB)L`+VZcN+qUiseZFu5{=WPaV1S-+_s)K|%OY;C60Zv*?VkWFS zA{sXX4bd+W=Y_s2xEr0RgU^nq(Y7fMJ%~_0GS={79H)YQ36i=3#`f{7PvI3@*w6IN;a$Lq; z@UuRHNC%qY9d6W*a?>KIg8Z>}9Ex+g!}>X|g2;RW*8>)yqOjL}s9$*i9K|o)<%jFQ zYm~hz`|HjCTfq{k>!#@lEqnZr()YeO)$p$B-wKy2EI=F4b%Lp4@^9DQ1U^%e=pF$i zvi(J}HJZPtpN2a0*ekDiAFd3#IJooyue<4u6wJeWk-->!Yppn7d?*DE7G zfF-;>*+!0d`TmM;V;a#ts}Nhw)c^-(^6<$_RGBhkwNGTP|L9ff22l^Tr%2PTNqXcG zuv*J+q(N6-1$yW?RQ_7_t%&}7r{m9HK(IRi_Yg_BIJcF|OS zm$Kxd6}$VzCjGDfl-{T&_OM&tm!1)Y1Z#72_RxsG;j?Kvpo776t{IlSa2G4M-26nrAX#y;Fb(FFSrQ#JCb&b98PITv&$p!Nt@44 zI0tsS6o8d8yu*qpLS-kB=kGOXZ-Z6kchf6KaePhaFP~Ec8!z}Y$86qW z#UFYQ*s-jB63U<3!m4$%5G@A@)&i|Uj@ru+af+=eBGO1MP$dkCCYFAOA$J98srK0A z?qwoHV;RP2AKb!4>OQbNkt9C%m^TpGAF9)N^kCeL(((e$8;CLF>(X${-)nf-w}sYd zIF2EcZJcCGu4ApRzQqJT6pAziO1P2>63gTU@S~M2v?-29EG5_D+TPS1Buq=&cxGvy z-9_!v}Vo*8ma@AUR`TA zSJ6cZkf)g*0x@iGggH~$+pdl%vx#b+Ia(%V>or)+As47+w3qX8_Trbyb_d!+6a@cd zXRm_vk-CoN#pz~@iFaogI8s4ev`FmOC>BMNbNPxefGnc+&_6u%J+J;Oh1D~i;D?l( zls-PD;XBujD39EW+z8q$=CbUdU}iI9=pnb$7bw|OSJ|voUKVojhR)QBmO>XJbi1vq z+ui#`GW2Qe7{_ufE!7H~8Mmy95wqlm@`XJ3xv)S;@=0H4*_=mZLR-Uu8CxG=*5Aco zfrPd9%Z;17zzy1>Rjgsh?SPwAI-$_iaedEtPc|#>Uzk0dkc^l|+TcdHt{ErK@~9ut z&{&Oj>F)O`Cnl!16qeR%OPV2gEOv`UYrs#geg5e!`CH8x4{3vW&z~!G zS0}2MoGX9%YIQYFuvo}Njm!eXD6Ge-XE&Rsy!yW0DZCOjcrx^$jN?>fb6Z7kt|jHa zZOCd~GJ()+#VB-^eS5w119)xkGeMPQR?*8?b@mLADXB7Vwq*{>SZ(!|xe7`9Itttp zVU@1yiO|Q@;Yr$%iDj*%b9OX-*h24*g$pUJfraSkj}?V^BxPUI23k##%wD}ur-d<= zss@@nB2e6^t67Lp}`CeQ!QywZ0(n`IPjFbHNgft%Jvw7tWB;P7P^>=HRC8qeZfHHRKb|`kj8<*-(3z}t&~SL$03%qInAApM?;nLRKlRlBYu~n_lZRStWhWxz z@~eefu$2ufZBw2drt>x!538-gjmY z&9m#~K+V;&LH^0j)Syk>pu|TH^_Ec&V-DXFa+dXeSw>JOBV6-S)pbDmV!$hqlCn;k z5rnAz@+Q2HyTaT)J(I_3MjW2(FzqM@>4&tzo)Bb-^EsR!eNUvADtP1lQg(B^H`W12 zekedtN3j~V#idw6`_*TtaP?Tk&!=}j2Oq7-Le9C&s*+B%@@`j4(>Y=M(MH<&E^v_e ztL=|S9poJCUkt3WdK9>PAUJLz=Mr$FH;}q~9gC#7{v^?#tKs|1!wnxRdUA~qPOKy$ zJK5~<(*T#x86mnQ2x+m4P^q{BW5Fj*S44Zpz{J0xsQtb|Khwk!6L4lVSW8nh#m$DZ zDVe9=#V~vuQxS1aH*shKx0|y%w0`h?#8dc4pEWD+LC0y<5021{v{LJcEXlkpKn}!^ zwc(~nC^<8Tk>^uq7ry{UKmTbQTq#;4PjNgvX*$iU#rh&4vEJ9JRH2S@dhf|>cyk~> zzE2%p0LtKwn6fD9_CeU%Kjp`F+n!qjQc!aa$%ix&Z%|BmxmQNr40*6(QuVvJ%;pyY zrEwSr&a9IsBPr03?#Etz%ZV4s2u(M_9}S?1@aDhDft)7UqXw=Y@#ns3vAf5f=9?$fuil4BP@Y&&Q#o~@d^iObIOSjya9_StBw+Bq_n~@psNJ4{AlZNyW z=+<8p-FMSXl_R^49&)MoP7fq2?tbjQ4+>21E*<`p+oysb562sNae6T;yFWEF0k%gt zyJ&2q_$pt?D`KjhrJSKwu9WkIuGF*mlwdNH#W-fbg_Oh^5!`^D!}$%`_;MwhKuDm3 zs%up5%J@Sx0Se!>hLzp91y>(v4tl}et$+Va)X=nZUssU8Kt^a&+o)P;RKbis=5=w~ zGd?37tgPwKTanD}y-un($xJ2=S2!gQ;{2RSTKk<7pcf=ue17I*hFw>VV>fdr&O*WG z;<*g~lQKTk%BejCYgm}sSEk5bt`Q!eC@Fc6!;oa{O46BEtl4^$WG#z1mllPxy^Cft z-W>7;qV>T(BY$8a1`!2Pg!SlX-eLHmy~uHKn7kh(Q0+dllxTuvIN5u=Zj9 z(W)2grzta3$2rKZ``90R3qUjmLwT!6U=lRZ8yV#1-Z5=GlDNM@q=SdU*}NEr0NE(G5DFJR zdiRO~fx;(VM*m9ftwBBVChXOnHt+IEWS06-!#;1+Hw+egumUfupi|@IpaxJS?m!!R zrbZ71hn*2dFzh$ETz~SEO}07)4T_dZ8)pyK6~e?F>gM6v+s9v_cX?9BJ{ zl@cm;KJ55&ze^ElfW@Z+;PxXd$)CD~9bOEhwLOp91S``Ae@@8m&*C7Xxu!sf zI&RNJ4q@*5z}{7h<59O`<(ZR&a1R9upQTkX{L)jR{8f@jCe;~o!iCUgZNaBkEXXdlM&erMLp3BOqYk;$FukydR#mC>XLs#Vtl^k`nWoNU8`)5;4Sn^_K(reoyX|d*2iE9vbQKd>qZ3Sljsx4k-Lr+$51W$0`JBGwbdK@Q2K>jI>QP~ zY#fV8tfv!M^G7ePzXyEW;Hto0zDTbi8fk8 zNuZjsPH(^^f&<<}4hqBR8!nB@&3Vf}lOneMQ3D=w;L4LQEL=`Fl3>eJr z=Qx_|3;LO;|&fTAU4Ym;>*9ocz^j>wIPa>OP}<%Ib+jH z4Z-(PVH5-G1H2^)pRGuxaI5rGE%G%PW{$c9NtVgpaj#TyFP`yY-|Fax;jvhep{#2k zZ>iIU%EbS+db6t#IV_r=I*|_0LhGIB-%V%iu#BLyQ_IxOKpRMoe{00L6Wl>`W#nyJ z%4b-@G-CukbhRVo3v63(n4F#tYrMXK4?f#cZ^kQDVzg3pArH$--M!@X$Oq|9VC>;U0pXrDAZ2_5jx2B)u`*&6-L9D!1I%z8Uvb#{CywRkF_p})5^^ZXdtthRvaxL zrT;z3gxD#0c&y}N>U9m5<=?peKxkFp7UHjpJa*3%`j;}X3z(g69davh;6lNb35^y% zdV<)1${&jYb)#ZdIO9+^4RG24x9R=OxAj;H`K-_j6E-hwzHIY3b~+7)vE71X#ShAj z9}QI=mHdrOi{X!v0*dOQr@(S3{X2psIylb`{8n#`Q~yh6eu^5g92QcGd$_+awf@}= zJHtYW8{!A&FBva-kaY?TN_Fq}5*qf(V0-d|KEINo2X>Kf#GVNB!ehjglh!%YxRRh` zf$GVR_w82asRG9HAIb(dh9CR&R4=|ilF@p5A1&>6)gzT^F$$aHaz*VN@)<*p>sX;2 z5^Md)bd1d5ukd!Omi3hQeEaif&Ei6yM@&AoL`YMfbYBd??suO>X;DPw>UaBtN!#q3 zOsz??qJb2L+;HkX4kR2NLavcI4q!T=Vy)1dLb#L9O^hQJ55W;zmGtFOI0`z7G&e~^ zM#Kr2jY+mf$zYIQji(C_@q!PMf*t$zGb~COFx|p1LU~jPFk5?c2zmVpG!Hq{{5j+}VA;9?$umgXB_!y0AEw(Bx z}z~x|Wk2`nShsJMcy5kVjUuh~!hF?eZ1(E`pLeU$F z%=({PL5J^ik)TDoTuc@9XkR3(Zei3wamXz8J<(3bUGv)4@{5NO39||YlF?Yte{%)? z6`EvkmIY$Ye0jDlXs*#7n*(5#Uxo2~oY>@_F)@*i9>dQ2&g+W!8S( zeUp=*9d|s>3-bTmkaf7lf66_2xKWH^cNi{d13{eON7Mv!n-aGnttoCSM?A$LLm46` z7_m?rZN;~;lR$OcEJQ_CMOr!%SycJN-5P9)?2_GkIAKJ!5pop7ig{*NkZ^$1#l(V6 zIvb|nFGQ475544aJ$@-Vhv(dA$AoRt$d0y{*k*gD(9S1W0O5S!0L@bh zrg21O>f2{1#l6$Ai~mjU?c!phCg;eBsaSRVT0Hi691y6}6|Jtia_etS)p`>oW~aW) zDDCg1{rQBXwy4AzrvntT#XDP&36Tl25CEf|1T9?%54le`wAy>$q>D z3@C8TVsRy`5h0r-nP(+Z>}QjE2T1T^TQl5f8C_BtUBOl=8{6!~6HT%O0W}Gdi=0$0 z^J?AHTwnu)6xF^PNj7Lp=9E_z_C_%uJRo^R+@#DaiZoPo1VC!DIWoBoMq+5<|(i zmx3#wmT4diIz~0Lct2^aoCXLG(8t1#yxxAF$mkg+YIVbcXK=tl>0FFcjr|c-4XQ0{M;@ zLVIdiF{_9m)!U!*VQ+<#1Q4i^beUH=Az%;Cw0bDP7Z?uwr> z$$p@ws!0Xb1PybF>h&2OQ9UBJqJyQ1&pl>3b-gh6};;c!e>Hm1H|b*8+J-8=e3D};>xFq45K z8B7j<`epq4;*hS03c@bLmN9BQjjoC-HL47q{qb7uy&h$v!rdh)i7~63VDP}P}HS^-wL95 zO(i;&07{c~Z=t(aLA*Q(!#oMflk;RsxX1oi1LiHG?IBoOMhz(%C|~SsL~k&nV2DUz+~7y+Iuw&xE0M)2+8cr{l|O z>#V2-gbu|xw5sJFnX7qL@Io`{MhQbvH~!B3E-og%XNIheo7JK^pNtT4x~0u+Q4k69 zE|F@G%B6$hPQl{7{vnYGlbzfpLgUsTr=vm{5~%?NmF4Jm!2z-DL>gw++pMmBZCmc9 zXhwgyL`lFcMwD4w+{-(Rc9ZzUyplU9)Nr;;YEa-Y1x&w{vvX7g1!4zfuPI-hC;Sa- z8lk6xP< zx0aB{Qbe7@w-P>OCR^*1BEJ+mVU?9Db=}7^@K2Bq$|iUO#$K3!tu|;}t$*vk@Zy7I zSvt+!tZv~I9#S3QoO?wu>`^}AAhT<-GN`so_iMX~M+INJIP&;FdsF7KN5;p1fK=bP z5Y1Vs*$1upY{N)`S`Q?Rmm98Xbspik(r8Ej{-c`j{Ceo=M1xr_YJMK^qYMf$g03qt zbx(sM6o)bFDtAJPQdxt_a;EQzF;wKQUXCW?pch#{&z-CSdS6EQIr*0WI0rFD%RKk4PKPq27fg3A z1AJnHSALWIS;Au6sJ8Ak=-MPPyhVF4b}}8uYnwUhxG?ygSlo#0;eH02v8$wX^sYx< z@ztWQl#7gc6{3aS#(V$NHP4y4y2tBwN{c?ww(!Xrg3_P3zeDZ|*(w4pF+JlmYeSaf zGz?A_yAVkQ)JfSyo*}3PqLS~#4oR&7j`{UXQ+}Ox`J5VEl-DODW{ocesxdHEzW@E} za8g`QCLIjFoJ>`S5xRvT-z^=o2S-4B}6|KMAE=t*iMMyUnYaXWq~}DfQ%p9zc=8 z6KI;_+;QIR-2V(RZ*PV#JU{zf1G$QBvFt>(Vpomdaj=rN`hftDyZ2xD}?Op}CX+AA}1Zi;m`cvE0WZ0KVV!sPHqds?ju$Xt=k? z?|;@x#N1pM!!&`+rg`+m(QVB4h>iSe0r|ap)oH6!vd8NJFO1K(%6?J`205hqEe3?LzW1)$_3{0 zR=%)mL$uD=Vr;w1u-H1_aaHN=mf|4)muZ;p^18;Fj*k}tF{ds|MbN6libe13ocD*~ z-7Y~yAY;Pn>+^8cpEh68p5pT`IGeOkUlV4kvNDRLAo&n0@OK9hGf8*qd^G;%DFf~J zC2ZrOfmQjd{ns=}?g@k?iGs?2ML|%AYQkX-#9i6o`if6<;lekyiRGrf@~T)PDLqZp zF;8X5Q=1fg^7#UpBQ7!*Hq_e%kA{NzR*p1TvmDXX=d^;Yk5?*kr~M->uF>{Z9@3sO zbvjn=>ohLl7g5glF%@yQImAe5!pq3yP#h;|NfAg6P=frd!u3jtYey%ruB451oPa82 z2GjG1#<$bPIzdi6aXPuD^dw23UFiO*uu0QIOP)TJB(4M7k`zt2cY^Xgi3minikR)@ z=&mbugcq1!HTZ?9h=Hl*s#M-G1w_XGFib_K>j(^cY%Kwo56}&VGIQSv?PUAh56K4h zxzk0w$pl%ux2s*S;MGPXpcuZQ56U}?A$MCH6W+2Zh+N0g*^b6Kik}T$gl`Vt0*_9ZGaM^Fkjq>|s?WOM} zrU@`8L<@I+h3yPdcv0=M#$uFppNPe7Nb}q@2l51GCMo z3CaD^_326EURrCo?p2gtmk5)+EO73rX>Ietzb7<`CjQ=<)}yW|VinH*TaxBf7xa}F z%`(6knZ|Fi1!8!kR;t+dV!ksTOcU%RhC0C~?E$nD zOrlYEwpq4>WEAI4ZWx9%<#3>6BFN^{jUv1IYWjZArwsfZ{eQb*Km>gnIH?maUV2{c zMW-J2W(n$rNKO(A5+{1t{o_D@urbVPp*rBs3e6H&EVK+~FpZx}$QvnKhA-R{f{pXg z0F2E`n`}hbV-$1!LIDv~Ho7=a{iqS)50>2~FbbWY5P$sZZ1iM6iCURqRR)C#k_rKb zv7bJHe9&_)DKzriHIP*7BQK^8L=DBj>-)CgN;)C?LVZ#w)G$#`2a^G*7=*B{`OT$kQ)iaOgI?qKyK${$`v+@ zK$F#?#HQiA6wtS#XgNje>H#KZ=HKfCKp@%Ck&Z8J>Z+2tiu~ik2(lVoS7YD z&2vjQ7K0Dil)e>Dj*DYmS8&>v7;A;9h(UB+!xz@_o?eXmC9wJ^YT=rN zlgy5{O`NdH*%-$DmlHs|2j7MBhQiT4HB!7lK`YMpG_)FV`%lK2e1&9m8AxStE3R_` z_B7oAbLSUhVS(fnd0@~9QO%1U!gG_@03Q!V_VwitA-lY9ovxgt1s3yHZyX@qLJCDu z7#2!4h(^X^Nl6?TKGSmTHbZ6HdG}DvltO{Uxf;aLto-*Jcklj5i@#hA+XS5{5A3<_ z1CDkRw`F9F<|9^q|4ZIoh}=z$>!X%Fb_5;0z&IjA?QUb!?Q2I8;nn~eeJwU#*4NQ! z8*x^7<`9(~J594b@Cq%*_jd+zffV-$PmL4gm~*u#q8_{;SL{`idO`qQSLcf3LYuDm z)su|{LA_r6F;=06+j;#0!y0Y>%NKS2MyzxrQ4iB+SPeCt12nGHVs37k#eCoHr$$&L zzxXec(Ok12-uXw&2l(aZNS&+9_@bS~VI>f${eudqJcu>uQ5OI5;OidCUvpwKNO<$S zQiJZwrvxm+NSR?JTr&6j)nKy7XyNl3I2Bb9-R|fO5^mZqjnaIzx+2!^rm`y}rV)ed zX3}kqTwVV~?a8eiR7&PWXG($}n2OI}H1=o_snS`n; z;(iNDa4cwg%MI7Oe{Rm~?!~(W-6`QRuE8@ql)hBjwwvIP^B z=*M|)JqMnlqj-8W3$X*~Z?ctNr{T(RtIwLgc=)&Aj^#pe?`M@KOdkhk>i3=(vo$EN zw8hk>auUEe^f0Z!qR?q1x+@i_z|O(K3h|lGE}>NeR6DOdwI!uCn8%=3*Qn9DZk=_I zkmiunlp>3+L9`lv20i0)&ZX4_4Bc8ng?*kRl#PK#Q^XVTQA=RD*qYRbF<(hYMCTvZzR*h33E*INxO3xql2tw?dLy^g(xb8HEefV z+C_9|&r#W<;1?sHH5_C1Ow+5``Bp>p)DIoJve;m5lphj^UBt9ch%M4r#DcmCRcV3{n>^9B={95vd6V- zbfp-8VGcM6&#q2it)Tr|EP3g@$O2Cp+Hv_}oP*S`eQr+sh1SfsQXNWwL`a!%oAeO_zurD-ewXpD@ zm=}U}q{DxK27Q{WDV{z{&2S7gNo!sMc*J^#%F~|NSWHo|O~cWP^XuKds6qE1qk0VBDuTEiV_U5VTy2QB7tzBkep0$pk#_7Rn_WMPmHB^|l zh;+%G^tw+&(YH;`JxiK{7@l(ZbGAd%{(A&~edk`~@gNe%;rENoNO618WGxCR;GhZy zdo7g!SLZ86`y|Om&6mYbP$|2fYxlxSa$bh@QieDw4*Lth!x(lrI% zICzLN<~^}T2MAwEj7;D+2uxK1Q-N5$p-uq+l$aSu=IpC-?fOLP0#Zznpk&0xk{`NR zr}Re=#A#KkjJ{_+eoMr`9oo&Hk)-xgt>5mIgPW>Q=j%|K?niRHBIWvhEtG7M2%Gj0 ziieXagUl?Rcm=tc4+|n5;afoYh+^bb8`b;5R#8K$o)`$pi^qph1t0gWsWeBr|U~8d*Ml22=&_z zLB0u1YTIIkPz^-`4gO~bnOVuZ;&?p5Tg|!S48doY;tnb?f}` zJ`#rdmMe0FtWdvSmlgBE6tJ0fq$**>X>>S?N(#n1Sh+5d??Moh#Y&Blop}ADN5--z z>%~kqPux=aWZ2_I=r?svkAPnScNC3`DG{t-cPa3}8r8i)4yj$0; zRgt{jdWadHobh#&3~uTWVd@b%OE-&KsBsATh-0s!R)k2m0pzE=JF;wvVvmu`Vp$o) z!I-WmQ0*CZLP1N2WA5M)57!14Wq{VmUUxCGHV11E$|8^DA^Y$LkjlRi^&TqR)-1AB zpPttJmf6Ce-Xt9kx;fNIl+K}Na9yT5eT2oBAsTEjhy-Gu&s#^NoF^xvL&~j=N5^{= zDtn9{eOwGjnTDLc(p(`dg_Lanb0>9)Ojf8r?&W=3Dbb_tc8T)Nh}))=dZF5t7}52% zbhU@c#2vnrnECv0G$~V>z8%NMx9+*(HfL)D^Cs?hy@2)yFgA|IzuNz9sp6bAhRa;m zvRcW>erlEFH|broX7fb$aJ`vP{oC{7oc8Rj$YJbcBgcVyJg^k$0cEkt%~YSi{)B)K zmW435evppkS~thGUjcJ^RDu7mDbt+=ZgXS#8kpFDEO_Uqf5}TR&51pV5U*=&(h&1- z+4hw2;~&!kvFFj$TX|FhS=Nsic#pDzwC5nKukf5=d4JRz0RD>^GdnWuQ~m$iD-Pg9 z)IF6Pip8d9IE}vF6l+AxOH#L0XkBkEpH#f;`EXFO&Qk}g{g0vFs{-7W6&LUwVSrdd zkZpsTJOc;nMX9n-^x{lTwnCDI46WBER!&?vskY~ps`iS!8}0JG_(Ku5xs#&|O2QkI zsE1h!E9#3(`g~;;LP3@Q0w8hwUI~J0ZGLBllrDn$?UO|*FeYeqVsugyy&%mTS(-*# z&AKb=1AFqc52X9L?%Y)Nw1Q1?8GRlfG-&KYRDq(VISl&|tVcy7aq);&Uud18zm6|o z$b9+l76&}qFn^7lYGo#UUz6ZI8bA{$FMqyC*}C59$DL6|g0e^vUD!5GB3^%Y3*aj0 zMv`>dK^9%@ph4{e()siB%RR0z7y?5ua|pFLay59kfOGKqm^Oqd(tX(l)mxLgF`|SE6ssWMuA33dMf$ z59{~XhC=dC$B#?4ESA+JS_~<{aJu|m_5j`C`I-CE#yInXpvlZK!kBn`8@48S2BE-& z(Hr!?kmBF}D9R#j>-v};H4(an9BZUQ6~(0`a+UnOREQh^F%CayF|Zt}b3PnVD7CIu z`G1D*_l-a#7t>+El@U`APDv{MG!aGu#Igf> z>rXeRrr}wUO7g0We*)zDdf`9_0L@y(TaVd-4 z?G`~nO9KMBR~XkSdaq446(ui?t6@q*UCrgjYjvXeTsLE7@K=(<*gpz%dyvlZ1xAU_ ztIk?tY7SDBR{ERfsV~uNUp#F1j{#hz9gdLSl13}SZYdj9wl|PpuoeJJ)y=jEV!F36 zBVhwXL%lcRo_i6wa9_OXavX0j3?69hxxg;j5_nb8J(o-VDUt(Aa7h|7UB8hXa04ey zRG;3a(kc3^LZ_<&?Mvu!Pby6gvNkZ5!nFe>!ZhRdOA{t~q)}qJgsWcC_8E0O+k)+X zk%~G#`#@c9tC)s!El-Ac=Ss3(K@P$z(|5$dNQay=y#l-t7&#Yc9iIMn;Pa99`2N&G ziv8Ar=;?H<8L|yZK|6B12G}5-k1(RC5_!TL4@E$pP+Hs!w&e(L5XHDH9P2tN756xZ zyU)w4pGVK5n=7dbttohYp7B{FwwEu4C?BU*WnwbLEj9DyyP-QOL1xk9m2TzKS5(r} zo8p(TAm?r2xAxZx6U&flS|yV&0`khbnC+alwRPeTYcQ%?TYyRBqLr#H=TDN)^wQ=& zO6ERF+^%&-!OVY>zKQ6biRhk*>UtqF0n~wTqAGY7U>$&~#bNn2c{3C%NUbHL_vB`2 zr*!*mAX6CiAxz;c!1UX!QwIe9U&O9We_|=5Le!vL$RHgp4V`IMe4u9iN1Ph2ZXQ?N zE1QqTBlOFZnVI~Dk*cWugxe0B+?-qU`#8+MD~cQR8l03`l0=Ccy@pgkfibtj5BKhp>sMAz`yWh&;)`y8;LiQWox!=+v`g+T{RwxtlO)9 z-mROVDOBCv-MO8Ad)H|bweK7W&yR)5JN9lrdJIXYagvW+pQJx>!Wla;L@wtpbLnWZ zntD+t#m5By)eau$bEueE?HYnS-rWyPpz~0q;FoKXnZ2otj&y z=@`2_RF(iBsDBcNQOYKfz*+Y?78@G14`q=3XONx^3Bd5zWt&5ZX_bQo`jFy2vOap- znOj!(05+ub9#ShSoGGHz0+i){Xl~=S`0spLN&2^XR0YX4A7E4kR5x5^iRj397ed2H z-HrH>0)ewV7!50#Cz7J(r^#$IWeS zjg|=Vqj_=HmO4?X=Kfua@%-`#7@y|NMONkZxZkVkAC48N^@`=CSk~ zkJfQ2%q;Z~2A1#-!i{PjGKa$RvNQhMYv+OnP|Ziv;22MDKH9s`&DC61^?z?sg$y}y3kGDwIZbRq zc*Av;Gf_eF1K0MQmnxt)i;tHDERt0XiYP74b@@n8LTFJKa{8HqYK#aB(|TCd>P>rz zbuA)eO?8tsx0QIabp+~J*t34iukvrqhFW>K)I>A2zZ(`jGyMK0Kd*^ejE{f$Asq7V z^6DRu4}5Myio~4$1E=-~ppahgtn&0MJ5r3HfDkc*j()2y_66N<8e3#ol=)_iwg&d- z^pp>g^{xWc@MyOvU_PTIK2C=#!(|I%(MFS4G#V^lZ_dpss*so@>L zwI%KML&pw;Q0jUwSfn>=((TXoE7 zp;;fL$HW#PV|s&ffo;@lW>J(7Q65-dpQ+A%7ESQdIf`u%9QLL+Bd9_?P+3~9)VIb@wjl~e) z4PqumEOlk>p9#u~k?7)&4t?4|UYYvmBjwS_?tb1`uIyRiBk;%62bjHg=`TPfNHFPW z;J6tHCv4-)v1!+MG>K|&-UxTnlS#M0Q-aR!=IuAUG2v~D z%FuPZKy@&}7{L7(C`mdH`)x=DX6WR!Y;W`!xuk{?B~SBKomhI^7HZQFE@jEtY#qQj z%ry$Wx8$~oT$7UURH5a51LN-TJ{YV)5TKj5f22J~7I_y-clC&YBk20yVP5TV7HTCV5CpJnE@pRgA#tdH z%cS?9yoVgjhX>Y?eGAkQutqQvRKnzn$Ip+l+FmWF<^pDuC_}CUr5(^~E{3&+ZYo9B zq#@V3{B25(>$|%M_xdZRlS{^AR7&DAwM|kIZvg~w*(o97TcL9$k2v5OT2YJ_H8=dK z$W|Rfp$;`Td=!)!te50jSz^B?_8~KwRzW6&K7oX(RJh#YPglm)NO3ybfe?#3{a2P9Rz|t`XuW&M-&{TxTq3?Mm?(8;Y>D`0yo$T(UhnIXfJIq zCY$G~RPUJwbaRcPR><%Ak|6E|Ct!=^X3cZaP6ap=;QrU*bmbFwyqbmzib8(97J)|&%lWqQawotA?^8r2e>VhoEb-s9yhhw zA2{>|Z~W^M^%h2u6l0RD`LRiMrDjQ`ZrLuOzGnHaM}lF3uSPw7Z{Zl_<{od3+d3+U1sc_Eowfv*j;$gES16Vnu|}y$t{%2Au#yoUp znuf_sBZG;;krkM$1EtWJ$7z@v5!Pu{FNOj?d!pV~j;DtTe68Bhv^oK3&Be&ce=jNL z7oU={IV@~FQW2;z*Q7!!+Z5yMG?x#>BkfZGmL-0AzKal0I)N6g%1flYwps%?ll#6^ zmu;lFKNi7Bqt?iSCUu{^NEt3_MH&BST;@**^q%mfBeGVIvf_9hOC3 FA9_j{7j| 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 literal 0 HcmV?d00001 diff --git a/tests/phpunit/data/images/avif-test-rotated.avif b/tests/phpunit/data/images/avif-test-rotated.avif new file mode 100644 index 0000000000000000000000000000000000000000..ee7c5246e13054a81f70f4affea1571381a88474 GIT binary patch literal 84837 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 literal 0 HcmV?d00001 From 1652946c145a0d1d6d5b7b4047c099e8b8cd565d Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 12 Jan 2024 08:46:05 -0700 Subject: [PATCH 13/79] match other test image names --- .../{avif-test-animated.avif => avif-animated.avif} | Bin .../{avif-test-lossless.avif => avif-lossless.avif} | Bin .../{avif-test-lossy.avif => avif-lossy.avif} | Bin .../{avif-test-rotated.avif => avif-rotated.avif} | Bin 4 files changed, 0 insertions(+), 0 deletions(-) rename tests/phpunit/data/images/{avif-test-animated.avif => avif-animated.avif} (100%) rename tests/phpunit/data/images/{avif-test-lossless.avif => avif-lossless.avif} (100%) rename tests/phpunit/data/images/{avif-test-lossy.avif => avif-lossy.avif} (100%) rename tests/phpunit/data/images/{avif-test-rotated.avif => avif-rotated.avif} (100%) diff --git a/tests/phpunit/data/images/avif-test-animated.avif b/tests/phpunit/data/images/avif-animated.avif similarity index 100% rename from tests/phpunit/data/images/avif-test-animated.avif rename to tests/phpunit/data/images/avif-animated.avif diff --git a/tests/phpunit/data/images/avif-test-lossless.avif b/tests/phpunit/data/images/avif-lossless.avif similarity index 100% rename from tests/phpunit/data/images/avif-test-lossless.avif rename to tests/phpunit/data/images/avif-lossless.avif diff --git a/tests/phpunit/data/images/avif-test-lossy.avif b/tests/phpunit/data/images/avif-lossy.avif similarity index 100% rename from tests/phpunit/data/images/avif-test-lossy.avif rename to tests/phpunit/data/images/avif-lossy.avif diff --git a/tests/phpunit/data/images/avif-test-rotated.avif b/tests/phpunit/data/images/avif-rotated.avif similarity index 100% rename from tests/phpunit/data/images/avif-test-rotated.avif rename to tests/phpunit/data/images/avif-rotated.avif From fd93e479e4a46222cee8d0eb8af6c6fe6c92a2ad Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 12 Jan 2024 08:50:03 -0700 Subject: [PATCH 14/79] one more test AVIF image --- tests/phpunit/data/images/avif-transparent.avif | Bin 0 -> 5378 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/phpunit/data/images/avif-transparent.avif diff --git a/tests/phpunit/data/images/avif-transparent.avif b/tests/phpunit/data/images/avif-transparent.avif new file mode 100644 index 0000000000000000000000000000000000000000..db34cd3f74c98b614e05b0095e1d7d37bccb96e8 GIT binary patch literal 5378 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 L_-8D&-ctM*x5@z@ literal 0 HcmV?d00001 From 345fa5e5baa367f960665d37947bd9202445cb24 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 12 Jan 2024 08:50:25 -0700 Subject: [PATCH 15/79] Add avif to get_image_mime tests --- tests/phpunit/tests/functions.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/phpunit/tests/functions.php b/tests/phpunit/tests/functions.php index 828184491d7b9..571bfa9d1d89d 100644 --- a/tests/phpunit/tests/functions.php +++ b/tests/phpunit/tests/functions.php @@ -1394,6 +1394,26 @@ public function data_wp_get_image_mime() { DIR_TESTDATA . '/uploads/dashicons.woff', false, ), + // Animated AVIF. + array( + DIR_TESTDATA . '/images/avif-animated.avif', + 'image/avif', + ), + // Lossless AVIF. + array( + DIR_TESTDATA . '/images/avif-lossless.avif', + 'image/avif', + ), + // Lossy AVIF. + array( + DIR_TESTDATA . '/images/avif-lossy.avif', + 'image/avif', + ), + // Transparent AVIF. + array( + DIR_TESTDATA . '/images/avif-transparent.avif', + 'image/avif', + ), ); return $data; From f86c77ebdee639f4f2ee6783b90bef67cbac1cdc Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 12 Jan 2024 08:55:35 -0700 Subject: [PATCH 16/79] Add AVIF to wp_getimagesize tests --- tests/phpunit/tests/functions.php | 44 +++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tests/phpunit/tests/functions.php b/tests/phpunit/tests/functions.php index 571bfa9d1d89d..8b18505a81431 100644 --- a/tests/phpunit/tests/functions.php +++ b/tests/phpunit/tests/functions.php @@ -1540,6 +1540,50 @@ public function data_wp_getimagesize() { DIR_TESTDATA . '/uploads/dashicons.woff', false, ), + // Animated AVIF. + array( + DIR_TESTDATA . '/images/avif-animated.avif', + array( + 150, + 150, + IMAGETYPE_AVIF, + 'width="150" height="150"', + 'mime' => 'image/avif', + ), + ), + // Lossless AVIF. + array( + DIR_TESTDATA . '/images/avif-lossless.avif', + array( + 400, + 400, + IMAGETYPE_AVIF, + 'width="400" height="400"', + 'mime' => 'image/avif', + ), + ), + // Lossy AVIF. + array( + DIR_TESTDATA . '/images/avif-lossy.avif', + array( + 400, + 400, + IMAGETYPE_AVIF, + 'width="400" height="400"', + 'mime' => 'image/avif', + ), + ), + // Transparent AVIF. + array( + DIR_TESTDATA . '/images/avif-transparent.avif', + array( + 128, + 128, + IMAGETYPE_AVIF, + 'width="128" height="128"', + 'mime' => 'image/avif', + ), + ), ); return $data; From fd88a992186b6e442b472547fd782db8a02f0887 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 12 Jan 2024 09:17:23 -0700 Subject: [PATCH 17/79] Add tests for wp_get_avif_info note: the file type is not determined by this function as with webp, but the sizes are returned correctly. --- tests/phpunit/tests/image/editor.php | 84 ++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/tests/phpunit/tests/image/editor.php b/tests/phpunit/tests/image/editor.php index bd54b803e2897..1244e761cf749 100644 --- a/tests/phpunit/tests/image/editor.php +++ b/tests/phpunit/tests/image/editor.php @@ -363,4 +363,88 @@ public function data_wp_get_webp_info() { ), ); } + + /** + * Test wp_get_avif_info. + * + * @ticket 51228 + * @dataProvider data_wp_get_avif_info + * + */ + public function test_wp_get_avif_info( $file, $expected ) { + $editor = wp_get_image_editor( $file ); + + if ( is_wp_error( $editor ) || ! $editor->supports_mime_type( 'image/avif' ) ) { + $this->markTestSkipped( sprintf( 'No AVIF support in the editor engine %s on this system.', $this->editor_engine ) ); + } + + $file_data = wp_get_avif_info( $file ); + $this->assertSame( $expected, $file_data ); + } + + /** + * Data provider for test_wp_get_avif_info(). + */ + public function data_wp_get_avif_info() { + return array( + // Standard JPEG. + array( + DIR_TESTDATA . '/images/test-image.jpg', + array( + 'width' => false, + 'height' => false, + 'type' => false, + ), + ), + // Standard GIF. + array( + DIR_TESTDATA . '/images/test-image.gif', + array( + 'width' => false, + 'height' => false, + 'type' => false, + ), + ), + // Animated AVIF. + array( + DIR_TESTDATA . '/images/avif-animated.avif', + array( + 'width' => 150, + 'height' => 150, + 'bit_depth' => 8, + 'num_channels' => 3, + ), + ), + // Lossless AVIF. + array( + DIR_TESTDATA . '/images/avif-lossless.avif', + array( + 'width' => 400, + 'height' => 400, + 'bit_depth' => 8, + 'num_channels' => 3, + ), + ), + // Lossy AVIF. + array( + DIR_TESTDATA . '/images/avif-lossy.avif', + array( + 'width' => 400, + 'height' => 400, + 'bit_depth' => 8, + 'num_channels' => 3, + ), + ), + // Transparent AVIF. + array( + DIR_TESTDATA . '/images/avif-transparent.avif', + array( + 'width' => 128, + 'height' => 128, + 'bit_depth' => 12, + 'num_channels' => 3, + ), + ), + ); + } } From 306870c8843abe7c1fc23d8cb55b97ab92f2c398 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 12 Jan 2024 09:19:38 -0700 Subject: [PATCH 18/79] Add AVIF to file_is_valid_image_positive tests --- tests/phpunit/tests/image/functions.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/phpunit/tests/image/functions.php b/tests/phpunit/tests/image/functions.php index f55b164496a84..85c00734edcc3 100644 --- a/tests/phpunit/tests/image/functions.php +++ b/tests/phpunit/tests/image/functions.php @@ -111,6 +111,10 @@ public function data_file_is_valid_image_positive() { 'webp-lossless.webp', 'webp-lossy.webp', 'webp-transparent.webp', + 'avif-animated.avif', + 'avif-lossless.avif', + 'avif-lossy.avif', + 'avif-transparent.avif', ); return $this->text_array_to_dataprovider( $files ); From b50890a54267676f4150a4a5c7559b20de9a1a21 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 12 Jan 2024 09:22:25 -0700 Subject: [PATCH 19/79] Add AVIF to file_is_displayable_image_positive test --- tests/phpunit/tests/image/functions.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/phpunit/tests/image/functions.php b/tests/phpunit/tests/image/functions.php index 85c00734edcc3..56c81a62f0c72 100644 --- a/tests/phpunit/tests/image/functions.php +++ b/tests/phpunit/tests/image/functions.php @@ -190,6 +190,17 @@ public function data_file_is_displayable_image_positive() { $files[] = 'webp-transparent.webp'; } + // Add AVIF images if the image editor supports them. + $file = DIR_TESTDATA . '/images/avif-lossless.avif'; + $editor = wp_get_image_editor( $file ); + + if ( ! is_wp_error( $editor ) && $editor->supports_mime_type( 'image/avif' ) ) { + $files[] = 'avif-animated.avif'; + $files[] = 'avif-lossless.avif'; + $files[] = 'avif-lossy.avif'; + $files[] = 'avif-transparent.avif'; + } + return $this->text_array_to_dataprovider( $files ); } From 8555bfbd1338d0bea7649647814721e75591fc1e Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 12 Jan 2024 10:03:23 -0700 Subject: [PATCH 20/79] Add additional magic file detection numbers for AVIF in wp_get_image_mime --- src/wp-includes/functions.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 562c3fb48f3a0..1c151aa1c8313 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -3355,15 +3355,20 @@ function wp_get_image_mime( $file ) { /** * Add AVIF fallback detection when image library doesn't support AVIF. * - * Note: detection values come from libavif. + * Note: detection values come from libavif and test files. * */ if ( - // avif. - ( str_starts_with( $magic, '0000002066' ) ) + // AVIF: lossless, lossy + ( str_starts_with( $magic, '0000002066' ) ) || + // AVIF: animated + ( str_starts_with( $magic, '0000002c66' ) ) || + // AVIF: transparent + ( str_starts_with( $magic, '0000001c66' ) ) ) { $mime = 'image/avif'; } + } catch ( Exception $e ) { $mime = false; } From e25becb9b1d1224a0020076d1679baa94aa668e9 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 12 Jan 2024 10:45:50 -0700 Subject: [PATCH 21/79] Correctly handle 0x0 sizes returned from `getimagesize`, using shim fallback for AVIFs --- src/wp-includes/media.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 5dd2e795de597..2dff3beaad0a8 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -5512,7 +5512,11 @@ function wp_getimagesize( $filename, array &$image_info = null ) { } } - if ( false !== $info ) { + if ( + false !== $info && + // Some PHP versions return 0x0 sizes from `getimagesize` for unrecognized image formats, including AVIFs + ! ( empty( $info[0] ) && empty( $info[1] ) ) + ) { return $info; } From f5b39b7be5320570cb24bea5dcc11fa39c745ad0 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 12 Jan 2024 10:53:31 -0700 Subject: [PATCH 22/79] Add test_resize_avif --- tests/phpunit/tests/image/resize.php | 28 ++++++++++++++++++++++++++++ wpmm | 1 + 2 files changed, 29 insertions(+) create mode 160000 wpmm diff --git a/tests/phpunit/tests/image/resize.php b/tests/phpunit/tests/image/resize.php index 5b302ce2958b8..1876f83382136 100644 --- a/tests/phpunit/tests/image/resize.php +++ b/tests/phpunit/tests/image/resize.php @@ -88,6 +88,34 @@ public function test_resize_webp() { $this->assertSame( IMAGETYPE_WEBP, $type ); } + /** + * Test resizing AVIF image. + * + * @ticket 51228 + * + */ + public function test_resize_avif() { + $file = DIR_TESTDATA . '/images/avif-lossy.avif'; + $editor = wp_get_image_editor( $file ); + + // Check if the editor supports the avif mime type. + if ( is_wp_error( $editor ) || ! $editor->supports_mime_type( 'image/avif' ) ) { + $this->markTestSkipped( sprintf( 'No AVIF support in the editor engine %s on this system.', $this->editor_engine ) ); + } + + $image = $this->resize_helper( $file, 25, 25 ); + + list( $w, $h, $type ) = wp_getimagesize( $image ); + + unlink( $image ); + + $this->assertSame( 'avif-lossy-25x25.avif', wp_basename( $image ) ); + $this->assertSame( 25, $w ); + $this->assertSame( 25, $h ); + $this->assertSame( IMAGETYPE_AVIF, $type ); + } + + public function test_resize_larger() { // image_resize() should refuse to make an image larger. $image = $this->resize_helper( DIR_TESTDATA . '/images/test-image.jpg', 100, 100 ); diff --git a/wpmm b/wpmm new file mode 160000 index 0000000000000..2b7610801bc36 --- /dev/null +++ b/wpmm @@ -0,0 +1 @@ +Subproject commit 2b7610801bc36f1eadc31e1b3d01ecc17217096d From 509ac34cceaac6ffe9f3e8a4ef90f9f28e608c84 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 12 Jan 2024 11:11:18 -0700 Subject: [PATCH 23/79] phpcs --- src/wp-includes/functions.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 1c151aa1c8313..f8f27fabc678d 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -3368,7 +3368,6 @@ function wp_get_image_mime( $file ) { ) { $mime = 'image/avif'; } - } catch ( Exception $e ) { $mime = false; } From 4c5efd024cf7bd7ae1a872fb57fa3ffd2eb381d4 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 12 Jan 2024 11:11:28 -0700 Subject: [PATCH 24/79] remove unused module --- wpmm | 1 - 1 file changed, 1 deletion(-) delete mode 160000 wpmm diff --git a/wpmm b/wpmm deleted file mode 160000 index 2b7610801bc36..0000000000000 --- a/wpmm +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2b7610801bc36f1eadc31e1b3d01ecc17217096d From b5d9624a61d73c6e2a57be91b50a52c32a556991 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 12 Jan 2024 12:05:35 -0700 Subject: [PATCH 25/79] temporarily log debug info for test runs --- src/wp-includes/media.php | 3 +++ tests/phpunit/tests/image/resize.php | 1 + 2 files changed, 4 insertions(+) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 2dff3beaad0a8..6f90f98f6fffb 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -5511,6 +5511,9 @@ function wp_getimagesize( $filename, array &$image_info = null ) { $info = @getimagesize( $filename ); } } + // Temporarily log the file info returned during test runs + error_log( print_r( $info, true ) ); + error_log( ! ( empty( $info[0] ) && empty( $info[1] ) ) ); if ( false !== $info && diff --git a/tests/phpunit/tests/image/resize.php b/tests/phpunit/tests/image/resize.php index 1876f83382136..615c12cb0c47d 100644 --- a/tests/phpunit/tests/image/resize.php +++ b/tests/phpunit/tests/image/resize.php @@ -92,6 +92,7 @@ public function test_resize_webp() { * Test resizing AVIF image. * * @ticket 51228 + * @ticket 99999 * */ public function test_resize_avif() { From 22f6af10f21c6c1d99c44533c351271efd320ea6 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 12 Jan 2024 14:10:07 -0700 Subject: [PATCH 26/79] more debug logging for tests --- tests/phpunit/tests/image/resize.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/phpunit/tests/image/resize.php b/tests/phpunit/tests/image/resize.php index 615c12cb0c47d..e21e70aaa6425 100644 --- a/tests/phpunit/tests/image/resize.php +++ b/tests/phpunit/tests/image/resize.php @@ -103,11 +103,20 @@ public function test_resize_avif() { if ( is_wp_error( $editor ) || ! $editor->supports_mime_type( 'image/avif' ) ) { $this->markTestSkipped( sprintf( 'No AVIF support in the editor engine %s on this system.', $this->editor_engine ) ); } - + error_log( 'test_resize_avif!!!' ); $image = $this->resize_helper( $file, 25, 25 ); + // Log image + error_log( print_r( $image, true ) ); + list( $w, $h, $type ) = wp_getimagesize( $image ); + // log w, h and type + error_log( 'w: ' . $w ); + error_log( 'h: ' . $h ); + error_log( 'type: ' . $type ); + + unlink( $image ); $this->assertSame( 'avif-lossy-25x25.avif', wp_basename( $image ) ); From 2ec1d34008362880d473998c82b20e1c7b2b32cd Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 12 Jan 2024 14:29:41 -0700 Subject: [PATCH 27/79] Imagick editor - fall back to `wp_getimagesize` --- src/wp-includes/class-wp-image-editor-imagick.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/wp-includes/class-wp-image-editor-imagick.php b/src/wp-includes/class-wp-image-editor-imagick.php index 6a92240cb300b..3a4a63530f5ad 100644 --- a/src/wp-includes/class-wp-image-editor-imagick.php +++ b/src/wp-includes/class-wp-image-editor-imagick.php @@ -265,6 +265,13 @@ protected function update_size( $width = null, $height = null ) { $height = $size['height']; } + // If we still don't have the image size, fall back to `$size = wp_getimagesize( $this->file );`. + if ( ! $width && ! $height && 'image/avif' === $this->mime_type ) { + $size = wp_getimagesize( $this->file ); + $width = $size[0]; + $height = $size[1]; + } + return parent::update_size( $width, $height ); } From 1b247ecbd7bcf3414699ed99b96996e495bc142f Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 12 Jan 2024 14:31:33 -0700 Subject: [PATCH 28/79] Improve doc block --- src/wp-includes/class-wp-image-editor-imagick.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-image-editor-imagick.php b/src/wp-includes/class-wp-image-editor-imagick.php index 3a4a63530f5ad..ffc3d7ea7761e 100644 --- a/src/wp-includes/class-wp-image-editor-imagick.php +++ b/src/wp-includes/class-wp-image-editor-imagick.php @@ -265,7 +265,8 @@ protected function update_size( $width = null, $height = null ) { $height = $size['height']; } - // If we still don't have the image size, fall back to `$size = wp_getimagesize( $this->file );`. + // If we still don't have the image size, fall back to `wp_getimagesize`. This ensures AVIF images + // are properly sized without affecting previous `getImageGeometry` behavior. if ( ! $width && ! $height && 'image/avif' === $this->mime_type ) { $size = wp_getimagesize( $this->file ); $width = $size[0]; From 547b40d6d6f97611f8d7cc663415e376900f224c Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 12 Jan 2024 15:59:13 -0700 Subject: [PATCH 29/79] more resize logging --- src/wp-includes/class-wp-image-editor-imagick.php | 2 ++ tests/phpunit/tests/image/resize.php | 12 +++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/class-wp-image-editor-imagick.php b/src/wp-includes/class-wp-image-editor-imagick.php index ffc3d7ea7761e..72db09c220f69 100644 --- a/src/wp-includes/class-wp-image-editor-imagick.php +++ b/src/wp-includes/class-wp-image-editor-imagick.php @@ -273,6 +273,8 @@ protected function update_size( $width = null, $height = null ) { $height = $size[1]; } + error_log( '---- update_size: ' . $width . 'x' . $height ); + return parent::update_size( $width, $height ); } diff --git a/tests/phpunit/tests/image/resize.php b/tests/phpunit/tests/image/resize.php index e21e70aaa6425..f9c5724a0ef5c 100644 --- a/tests/phpunit/tests/image/resize.php +++ b/tests/phpunit/tests/image/resize.php @@ -107,14 +107,10 @@ public function test_resize_avif() { $image = $this->resize_helper( $file, 25, 25 ); // Log image - error_log( print_r( $image, true ) ); + error_log( json_encode( $image, JSON_PRETTY_PRINT ) ); list( $w, $h, $type ) = wp_getimagesize( $image ); - // log w, h and type - error_log( 'w: ' . $w ); - error_log( 'h: ' . $h ); - error_log( 'type: ' . $type ); unlink( $image ); @@ -234,8 +230,14 @@ protected function resize_helper( $file, $width, $height, $crop = false ) { return $editor; } + // log editor class name + error_log( 'editor: ' . get_class( $editor ) ); + $resized = $editor->resize( $width, $height, $crop ); + // log resized + error_log( 'resized: ' . json_encode( $resized, JSON_PRETTY_PRINT ) ); + if ( is_wp_error( $resized ) ) { return $resized; } From 07dbc247e081700ff52716d34ae6606a959a55df Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 12 Jan 2024 16:00:14 -0700 Subject: [PATCH 30/79] remove image size logging --- src/wp-includes/media.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 6f90f98f6fffb..2dff3beaad0a8 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -5511,9 +5511,6 @@ function wp_getimagesize( $filename, array &$image_info = null ) { $info = @getimagesize( $filename ); } } - // Temporarily log the file info returned during test runs - error_log( print_r( $info, true ) ); - error_log( ! ( empty( $info[0] ) && empty( $info[1] ) ) ); if ( false !== $info && From 74eb3c8554dc8bd4551fbeedfdd7b8d2ff1aef02 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 12 Jan 2024 16:24:56 -0700 Subject: [PATCH 31/79] remove error logging --- src/wp-includes/class-wp-image-editor-imagick.php | 2 -- tests/phpunit/tests/image/resize.php | 13 +------------ 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/src/wp-includes/class-wp-image-editor-imagick.php b/src/wp-includes/class-wp-image-editor-imagick.php index 72db09c220f69..ffc3d7ea7761e 100644 --- a/src/wp-includes/class-wp-image-editor-imagick.php +++ b/src/wp-includes/class-wp-image-editor-imagick.php @@ -273,8 +273,6 @@ protected function update_size( $width = null, $height = null ) { $height = $size[1]; } - error_log( '---- update_size: ' . $width . 'x' . $height ); - return parent::update_size( $width, $height ); } diff --git a/tests/phpunit/tests/image/resize.php b/tests/phpunit/tests/image/resize.php index f9c5724a0ef5c..615c12cb0c47d 100644 --- a/tests/phpunit/tests/image/resize.php +++ b/tests/phpunit/tests/image/resize.php @@ -103,16 +103,11 @@ public function test_resize_avif() { if ( is_wp_error( $editor ) || ! $editor->supports_mime_type( 'image/avif' ) ) { $this->markTestSkipped( sprintf( 'No AVIF support in the editor engine %s on this system.', $this->editor_engine ) ); } - error_log( 'test_resize_avif!!!' ); - $image = $this->resize_helper( $file, 25, 25 ); - // Log image - error_log( json_encode( $image, JSON_PRETTY_PRINT ) ); + $image = $this->resize_helper( $file, 25, 25 ); list( $w, $h, $type ) = wp_getimagesize( $image ); - - unlink( $image ); $this->assertSame( 'avif-lossy-25x25.avif', wp_basename( $image ) ); @@ -230,14 +225,8 @@ protected function resize_helper( $file, $width, $height, $crop = false ) { return $editor; } - // log editor class name - error_log( 'editor: ' . get_class( $editor ) ); - $resized = $editor->resize( $width, $height, $crop ); - // log resized - error_log( 'resized: ' . json_encode( $resized, JSON_PRETTY_PRINT ) ); - if ( is_wp_error( $resized ) ) { return $resized; } From 82ccce5f03a9e9fec334e7507a11e38a8c75bac3 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 12 Jan 2024 16:46:15 -0700 Subject: [PATCH 32/79] log and skip failing test --- tests/phpunit/tests/image/resize.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/phpunit/tests/image/resize.php b/tests/phpunit/tests/image/resize.php index 615c12cb0c47d..5c717400866bd 100644 --- a/tests/phpunit/tests/image/resize.php +++ b/tests/phpunit/tests/image/resize.php @@ -108,6 +108,14 @@ public function test_resize_avif() { list( $w, $h, $type ) = wp_getimagesize( $image ); + if ( null === $w ) { + error_log( 'failed to load image size from ' . $image ); + error_log( wp_getimagesize( $image ) ); + + // exit test + $this->markTestSkipped( 'failed to load image size' ); + } + unlink( $image ); $this->assertSame( 'avif-lossy-25x25.avif', wp_basename( $image ) ); From 1823866d8c450ae77e29376b9819695d54baaf56 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 12 Jan 2024 22:53:52 -0700 Subject: [PATCH 33/79] remove fallback --- tests/phpunit/tests/image/resize.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/phpunit/tests/image/resize.php b/tests/phpunit/tests/image/resize.php index 5c717400866bd..615c12cb0c47d 100644 --- a/tests/phpunit/tests/image/resize.php +++ b/tests/phpunit/tests/image/resize.php @@ -108,14 +108,6 @@ public function test_resize_avif() { list( $w, $h, $type ) = wp_getimagesize( $image ); - if ( null === $w ) { - error_log( 'failed to load image size from ' . $image ); - error_log( wp_getimagesize( $image ) ); - - // exit test - $this->markTestSkipped( 'failed to load image size' ); - } - unlink( $image ); $this->assertSame( 'avif-lossy-25x25.avif', wp_basename( $image ) ); From e5ec13c68d45d5c9ed25c8a2890b3268f51c11b2 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Sat, 13 Jan 2024 20:48:36 -0700 Subject: [PATCH 34/79] log mime type check in imagick --- src/wp-includes/class-wp-image-editor-imagick.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-image-editor-imagick.php b/src/wp-includes/class-wp-image-editor-imagick.php index ffc3d7ea7761e..e47b685b4ab1a 100644 --- a/src/wp-includes/class-wp-image-editor-imagick.php +++ b/src/wp-includes/class-wp-image-editor-imagick.php @@ -109,7 +109,7 @@ public static function supports_mime_type( $mime_type ) { if ( ! method_exists( 'Imagick', 'setIteratorIndex' ) && 'image/jpeg' !== $mime_type ) { return false; } - + error_log(' format ' . $imagick_extension . ' result ' . Imagick::queryFormats( $imagick_extension ) . " all ". Imagick::queryFormats( $imagick_extension ) ); try { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged return ( (bool) @Imagick::queryFormats( $imagick_extension ) ); From 055859fc53e7cba4e1c78ad57debc4dfeb7a752d Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Sat, 13 Jan 2024 20:59:13 -0700 Subject: [PATCH 35/79] better logging --- src/wp-includes/class-wp-image-editor-imagick.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-image-editor-imagick.php b/src/wp-includes/class-wp-image-editor-imagick.php index e47b685b4ab1a..5b6ccb83fb76a 100644 --- a/src/wp-includes/class-wp-image-editor-imagick.php +++ b/src/wp-includes/class-wp-image-editor-imagick.php @@ -109,7 +109,7 @@ public static function supports_mime_type( $mime_type ) { if ( ! method_exists( 'Imagick', 'setIteratorIndex' ) && 'image/jpeg' !== $mime_type ) { return false; } - error_log(' format ' . $imagick_extension . ' result ' . Imagick::queryFormats( $imagick_extension ) . " all ". Imagick::queryFormats( $imagick_extension ) ); + error_log(' format ' . json_encode($imagick_extension) . ' result ' . json_encode(Imagick::queryFormats( $imagick_extension ) ) . " all ". json_encode( Imagick::queryFormats( $imagick_extension ) ) ); try { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged return ( (bool) @Imagick::queryFormats( $imagick_extension ) ); From 117de6da2fd90729b8193d00653362ea1f38b454 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 14 Jan 2024 08:23:13 -0700 Subject: [PATCH 36/79] add logging of failed avif test --- src/wp-includes/class-wp-image-editor-gd.php | 1 + src/wp-includes/class-wp-image-editor-imagick.php | 3 ++- tests/phpunit/tests/image/resize.php | 9 +++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-image-editor-gd.php b/src/wp-includes/class-wp-image-editor-gd.php index 596bfc750ca09..2b5b0a054e57f 100644 --- a/src/wp-includes/class-wp-image-editor-gd.php +++ b/src/wp-includes/class-wp-image-editor-gd.php @@ -62,6 +62,7 @@ public static function test( $args = array() ) { */ public static function supports_mime_type( $mime_type ) { $image_types = imagetypes(); + error_log ( "GD image types: $image_types" ); switch ( $mime_type ) { case 'image/jpeg': return ( $image_types & IMG_JPG ) != 0; diff --git a/src/wp-includes/class-wp-image-editor-imagick.php b/src/wp-includes/class-wp-image-editor-imagick.php index 5b6ccb83fb76a..19d184847a53d 100644 --- a/src/wp-includes/class-wp-image-editor-imagick.php +++ b/src/wp-includes/class-wp-image-editor-imagick.php @@ -109,7 +109,8 @@ public static function supports_mime_type( $mime_type ) { if ( ! method_exists( 'Imagick', 'setIteratorIndex' ) && 'image/jpeg' !== $mime_type ) { return false; } - error_log(' format ' . json_encode($imagick_extension) . ' result ' . json_encode(Imagick::queryFormats( $imagick_extension ) ) . " all ". json_encode( Imagick::queryFormats( $imagick_extension ) ) ); + // log imagick supported + error_log( 'Imagick supported: ' . (bool) @Imagick::queryFormats( $imagick_extension ) ); try { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged return ( (bool) @Imagick::queryFormats( $imagick_extension ) ); diff --git a/tests/phpunit/tests/image/resize.php b/tests/phpunit/tests/image/resize.php index 615c12cb0c47d..571e74b1599fc 100644 --- a/tests/phpunit/tests/image/resize.php +++ b/tests/phpunit/tests/image/resize.php @@ -106,7 +106,16 @@ public function test_resize_avif() { $image = $this->resize_helper( $file, 25, 25 ); + list( $w, $h, $type ) = wp_getimagesize( $image ); + if ( null === $w ) { + // Log the editor type and skip the test. + error_log( json_encode( $image, JSON_PRETTY_PRINT ) ); + error_log( json_encode( $editor, JSON_PRETTY_PRINT ) ); + error_log( json_encode( wp_get_image_editor( $editor), JSON_PRETTY_PRINT ) ); + error_log( json_encode( $editor->supports_mime_type( 'image/avif' ), JSON_PRETTY_PRINT ) ); + $this->markTestSkipped( sprintf( 'No AVIF support in the editor engine %s on this system.', $this->editor_engine ) ); + } unlink( $image ); From 67291a63e8b24babe83495c79f6e11b7d7d8528e Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 14 Jan 2024 08:41:52 -0700 Subject: [PATCH 37/79] improve logging --- tests/phpunit/tests/image/resize.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/phpunit/tests/image/resize.php b/tests/phpunit/tests/image/resize.php index 571e74b1599fc..d9fa023342785 100644 --- a/tests/phpunit/tests/image/resize.php +++ b/tests/phpunit/tests/image/resize.php @@ -110,10 +110,10 @@ public function test_resize_avif() { list( $w, $h, $type ) = wp_getimagesize( $image ); if ( null === $w ) { // Log the editor type and skip the test. - error_log( json_encode( $image, JSON_PRETTY_PRINT ) ); - error_log( json_encode( $editor, JSON_PRETTY_PRINT ) ); - error_log( json_encode( wp_get_image_editor( $editor), JSON_PRETTY_PRINT ) ); - error_log( json_encode( $editor->supports_mime_type( 'image/avif' ), JSON_PRETTY_PRINT ) ); + error_log( "image: " . json_encode( $image, JSON_PRETTY_PRINT ) ); + error_log( "editor: " . json_encode( $editor, JSON_PRETTY_PRINT ) ); + error_log( "editor for image: " . json_encode( wp_get_image_editor( $image), JSON_PRETTY_PRINT ) ); + error_log( "editor supports avif: " . json_encode( $editor->supports_mime_type( 'image/avif' ), JSON_PRETTY_PRINT ) ); $this->markTestSkipped( sprintf( 'No AVIF support in the editor engine %s on this system.', $this->editor_engine ) ); } From e43c03e3e9126eab774adfbdbc6830cd52870b0d Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 14 Jan 2024 09:28:50 -0700 Subject: [PATCH 38/79] improve logging --- tests/phpunit/tests/image/resize.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/phpunit/tests/image/resize.php b/tests/phpunit/tests/image/resize.php index d9fa023342785..babdc1cd848f1 100644 --- a/tests/phpunit/tests/image/resize.php +++ b/tests/phpunit/tests/image/resize.php @@ -110,11 +110,12 @@ public function test_resize_avif() { list( $w, $h, $type ) = wp_getimagesize( $image ); if ( null === $w ) { // Log the editor type and skip the test. + error_log( '!!!' ); error_log( "image: " . json_encode( $image, JSON_PRETTY_PRINT ) ); error_log( "editor: " . json_encode( $editor, JSON_PRETTY_PRINT ) ); - error_log( "editor for image: " . json_encode( wp_get_image_editor( $image), JSON_PRETTY_PRINT ) ); + error_log( "editor for image after: " . json_encode( get_class( wp_get_image_editor( $image ) ), JSON_PRETTY_PRINT ) ); error_log( "editor supports avif: " . json_encode( $editor->supports_mime_type( 'image/avif' ), JSON_PRETTY_PRINT ) ); - $this->markTestSkipped( sprintf( 'No AVIF support in the editor engine %s on this system.', $this->editor_engine ) ); + $this->markTestSkipped( sprintf( '!!! No AVIF support in the editor engine %s on this system.', $this->editor_engine ) ); } unlink( $image ); From 13641c03c29cd4f7a2be43b7b9e9c29d744f6463 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 14 Jan 2024 09:32:08 -0700 Subject: [PATCH 39/79] class name for log --- tests/phpunit/tests/image/resize.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/image/resize.php b/tests/phpunit/tests/image/resize.php index babdc1cd848f1..5d9c2ca1fb973 100644 --- a/tests/phpunit/tests/image/resize.php +++ b/tests/phpunit/tests/image/resize.php @@ -112,7 +112,7 @@ public function test_resize_avif() { // Log the editor type and skip the test. error_log( '!!!' ); error_log( "image: " . json_encode( $image, JSON_PRETTY_PRINT ) ); - error_log( "editor: " . json_encode( $editor, JSON_PRETTY_PRINT ) ); + error_log( "editor: " . json_encode( get_class( $editor ), JSON_PRETTY_PRINT ) ); error_log( "editor for image after: " . json_encode( get_class( wp_get_image_editor( $image ) ), JSON_PRETTY_PRINT ) ); error_log( "editor supports avif: " . json_encode( $editor->supports_mime_type( 'image/avif' ), JSON_PRETTY_PRINT ) ); $this->markTestSkipped( sprintf( '!!! No AVIF support in the editor engine %s on this system.', $this->editor_engine ) ); From 3e9745f9f4a1c3fd4c6fd8bedf7ebd178e11cf0b Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 14 Jan 2024 09:43:19 -0700 Subject: [PATCH 40/79] log avifinfo results after resize --- src/wp-includes/media.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 2dff3beaad0a8..137ab5ec5ff83 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -5552,6 +5552,11 @@ function wp_getimagesize( $filename, array &$image_info = null ) { $width = $avif_info['width']; $height = $avif_info['height']; + // log the avifinfo results + error_log( '!!! AVIF info' ); + error_log( print_r( $avif_info, true ) ); + error_log( print_r( $filename ) ); + // Mimic the native return format. if ( $width && $height ) { return array( From 63664bcb4fb38c2af77279f2658d2bc0c85c2d5c Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 14 Jan 2024 13:31:50 -0700 Subject: [PATCH 41/79] skip if either h or w empty --- src/wp-includes/media.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 137ab5ec5ff83..b4b1791de4f80 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -5515,7 +5515,7 @@ function wp_getimagesize( $filename, array &$image_info = null ) { if ( false !== $info && // Some PHP versions return 0x0 sizes from `getimagesize` for unrecognized image formats, including AVIFs - ! ( empty( $info[0] ) && empty( $info[1] ) ) + ! ( empty( $info[0] ) || empty( $info[1] ) ) ) { return $info; } From b257474dbd1a49195c494872deacf3955af13afd Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 14 Jan 2024 13:32:08 -0700 Subject: [PATCH 42/79] more logging --- tests/phpunit/tests/image/resize.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/phpunit/tests/image/resize.php b/tests/phpunit/tests/image/resize.php index 5d9c2ca1fb973..3cebebaa99440 100644 --- a/tests/phpunit/tests/image/resize.php +++ b/tests/phpunit/tests/image/resize.php @@ -112,6 +112,7 @@ public function test_resize_avif() { // Log the editor type and skip the test. error_log( '!!!' ); error_log( "image: " . json_encode( $image, JSON_PRETTY_PRINT ) ); + error_log( "sizes from wp_getimagesize: " . json_encode( wp_getimagesize( $image ), JSON_PRETTY_PRINT ) ); error_log( "editor: " . json_encode( get_class( $editor ), JSON_PRETTY_PRINT ) ); error_log( "editor for image after: " . json_encode( get_class( wp_get_image_editor( $image ) ), JSON_PRETTY_PRINT ) ); error_log( "editor supports avif: " . json_encode( $editor->supports_mime_type( 'image/avif' ), JSON_PRETTY_PRINT ) ); From bc38fd4707562a9a50ec15a39f143de9de916cff Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 14 Jan 2024 13:51:09 -0700 Subject: [PATCH 43/79] more logging --- src/wp-includes/media.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index b4b1791de4f80..1fc312c1dbaab 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -5511,6 +5511,13 @@ function wp_getimagesize( $filename, array &$image_info = null ) { $info = @getimagesize( $filename ); } } +// Log results so far +error_log( '!!! getimagesize' ); +error_log( print_r( $info, true ) ); + +// Log image mime +error_log( print_r( wp_get_image_mime( $filename ), true ) ); + if ( false !== $info && @@ -5549,13 +5556,13 @@ function wp_getimagesize( $filename, array &$image_info = null ) { // extract the image size info from the file headers. if ( 'image/avif' === wp_get_image_mime( $filename ) ) { $avif_info = wp_get_avif_info( $filename ); - $width = $avif_info['width']; - $height = $avif_info['height']; - // log the avifinfo results error_log( '!!! AVIF info' ); error_log( print_r( $avif_info, true ) ); error_log( print_r( $filename ) ); + $width = $avif_info['width']; + $height = $avif_info['height']; + // Mimic the native return format. if ( $width && $height ) { From 0195d41c42aa038f625218212a22288393ffcad6 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 14 Jan 2024 13:54:10 -0700 Subject: [PATCH 44/79] more logging --- tests/phpunit/tests/image/resize.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/phpunit/tests/image/resize.php b/tests/phpunit/tests/image/resize.php index 3cebebaa99440..016018cf349d6 100644 --- a/tests/phpunit/tests/image/resize.php +++ b/tests/phpunit/tests/image/resize.php @@ -105,6 +105,10 @@ public function test_resize_avif() { } $image = $this->resize_helper( $file, 25, 25 ); + error_log( "in test image: " . json_encode( $image, JSON_PRETTY_PRINT ) ); + + // file exists? + error_log( "file exists: " . json_encode( file_exists( $image ), JSON_PRETTY_PRINT ) ); list( $w, $h, $type ) = wp_getimagesize( $image ); From efd37b97d2a8ce67f5807b2dd0341452200b20ae Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 14 Jan 2024 14:45:43 -0700 Subject: [PATCH 45/79] log mime magics --- src/wp-includes/functions.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index f8f27fabc678d..82a14fe2688f6 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -3337,6 +3337,9 @@ function wp_get_image_mime( $file ) { return false; } + error_log( "wp_get_image_mime() magic: $magic" + + /* * Add WebP fallback detection when image library doesn't support WebP. * Note: detection values come from LibWebP, see @@ -3352,6 +3355,9 @@ function wp_get_image_mime( $file ) { $mime = 'image/webp'; } + // log the magic # + error_log( "binhex() magic: $magic" + /** * Add AVIF fallback detection when image library doesn't support AVIF. * From c6c3b5496918b47713fa93fc5bf291ce106f97da Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 14 Jan 2024 14:46:20 -0700 Subject: [PATCH 46/79] adjust empty logic, log after check --- src/wp-includes/media.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 1fc312c1dbaab..a3d10c4552ac9 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -5520,13 +5520,13 @@ function wp_getimagesize( $filename, array &$image_info = null ) { if ( - false !== $info && + ! empty( $info ) && // Some PHP versions return 0x0 sizes from `getimagesize` for unrecognized image formats, including AVIFs - ! ( empty( $info[0] ) || empty( $info[1] ) ) + ! ( empty( $info[0] ) && empty( $info[1] ) ) ) { return $info; } - + error_log( '!!! getimagesize failed' ); /* * For PHP versions that don't support WebP images, * extract the image size info from the file headers. From 7e512189699ad118799c85d9b541d8b25c0962fa Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 14 Jan 2024 14:46:31 -0700 Subject: [PATCH 47/79] log a mime check in test --- tests/phpunit/tests/image/resize.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/phpunit/tests/image/resize.php b/tests/phpunit/tests/image/resize.php index 016018cf349d6..d212836b1c9bb 100644 --- a/tests/phpunit/tests/image/resize.php +++ b/tests/phpunit/tests/image/resize.php @@ -110,6 +110,8 @@ public function test_resize_avif() { // file exists? error_log( "file exists: " . json_encode( file_exists( $image ), JSON_PRETTY_PRINT ) ); + // log mime type of resized image + error_log( "mime type of resized image: " . json_encode( wp_get_image_mime( $image ), JSON_PRETTY_PRINT ) ); list( $w, $h, $type ) = wp_getimagesize( $image ); if ( null === $w ) { From 569fe2794ad7d242df8a66500cd5f49d147b935c Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 14 Jan 2024 15:13:52 -0700 Subject: [PATCH 48/79] fix typo --- src/wp-includes/functions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 82a14fe2688f6..79866e6bf5169 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -3337,7 +3337,7 @@ function wp_get_image_mime( $file ) { return false; } - error_log( "wp_get_image_mime() magic: $magic" + error_log( "wp_get_image_mime() magic: $magic" ); /* @@ -3356,7 +3356,7 @@ function wp_get_image_mime( $file ) { } // log the magic # - error_log( "binhex() magic: $magic" + error_log( "binhex() magic: $magic" ); /** * Add AVIF fallback detection when image library doesn't support AVIF. From b8290a9c22c5ee2568242373d51ca69450f0b35f Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 14 Jan 2024 16:15:43 -0700 Subject: [PATCH 49/79] add additional magic for AVIF detection --- src/wp-includes/functions.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 79866e6bf5169..5e88584dd6678 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -3367,6 +3367,7 @@ function wp_get_image_mime( $file ) { if ( // AVIF: lossless, lossy ( str_starts_with( $magic, '0000002066' ) ) || + ( str_starts_with( $magic, '0000001866' ) ) || // magic from tests // AVIF: animated ( str_starts_with( $magic, '0000002c66' ) ) || // AVIF: transparent From 6c10a037f4b18fbb5681b23444fe2f53f5ec2cd3 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 14 Jan 2024 16:50:41 -0700 Subject: [PATCH 50/79] remove logging --- src/wp-includes/class-wp-image-editor-gd.php | 2 +- .../class-wp-image-editor-imagick.php | 3 +-- src/wp-includes/functions.php | 6 ------ src/wp-includes/media.php | 15 ++------------- tests/phpunit/tests/image/resize.php | 17 ----------------- 5 files changed, 4 insertions(+), 39 deletions(-) diff --git a/src/wp-includes/class-wp-image-editor-gd.php b/src/wp-includes/class-wp-image-editor-gd.php index 2b5b0a054e57f..bc4595c42178f 100644 --- a/src/wp-includes/class-wp-image-editor-gd.php +++ b/src/wp-includes/class-wp-image-editor-gd.php @@ -62,7 +62,7 @@ public static function test( $args = array() ) { */ public static function supports_mime_type( $mime_type ) { $image_types = imagetypes(); - error_log ( "GD image types: $image_types" ); + switch ( $mime_type ) { case 'image/jpeg': return ( $image_types & IMG_JPG ) != 0; diff --git a/src/wp-includes/class-wp-image-editor-imagick.php b/src/wp-includes/class-wp-image-editor-imagick.php index 19d184847a53d..ffc3d7ea7761e 100644 --- a/src/wp-includes/class-wp-image-editor-imagick.php +++ b/src/wp-includes/class-wp-image-editor-imagick.php @@ -109,8 +109,7 @@ public static function supports_mime_type( $mime_type ) { if ( ! method_exists( 'Imagick', 'setIteratorIndex' ) && 'image/jpeg' !== $mime_type ) { return false; } - // log imagick supported - error_log( 'Imagick supported: ' . (bool) @Imagick::queryFormats( $imagick_extension ) ); + try { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged return ( (bool) @Imagick::queryFormats( $imagick_extension ) ); diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 5e88584dd6678..a1be67844342c 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -3337,9 +3337,6 @@ function wp_get_image_mime( $file ) { return false; } - error_log( "wp_get_image_mime() magic: $magic" ); - - /* * Add WebP fallback detection when image library doesn't support WebP. * Note: detection values come from LibWebP, see @@ -3355,9 +3352,6 @@ function wp_get_image_mime( $file ) { $mime = 'image/webp'; } - // log the magic # - error_log( "binhex() magic: $magic" ); - /** * Add AVIF fallback detection when image library doesn't support AVIF. * diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index a3d10c4552ac9..eb055d267c9eb 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -5511,13 +5511,6 @@ function wp_getimagesize( $filename, array &$image_info = null ) { $info = @getimagesize( $filename ); } } -// Log results so far -error_log( '!!! getimagesize' ); -error_log( print_r( $info, true ) ); - -// Log image mime -error_log( print_r( wp_get_image_mime( $filename ), true ) ); - if ( ! empty( $info ) && @@ -5526,7 +5519,7 @@ function wp_getimagesize( $filename, array &$image_info = null ) { ) { return $info; } - error_log( '!!! getimagesize failed' ); + /* * For PHP versions that don't support WebP images, * extract the image size info from the file headers. @@ -5556,14 +5549,10 @@ function wp_getimagesize( $filename, array &$image_info = null ) { // extract the image size info from the file headers. if ( 'image/avif' === wp_get_image_mime( $filename ) ) { $avif_info = wp_get_avif_info( $filename ); - // log the avifinfo results - error_log( '!!! AVIF info' ); - error_log( print_r( $avif_info, true ) ); - error_log( print_r( $filename ) ); + $width = $avif_info['width']; $height = $avif_info['height']; - // Mimic the native return format. if ( $width && $height ) { return array( diff --git a/tests/phpunit/tests/image/resize.php b/tests/phpunit/tests/image/resize.php index d212836b1c9bb..615c12cb0c47d 100644 --- a/tests/phpunit/tests/image/resize.php +++ b/tests/phpunit/tests/image/resize.php @@ -105,25 +105,8 @@ public function test_resize_avif() { } $image = $this->resize_helper( $file, 25, 25 ); - error_log( "in test image: " . json_encode( $image, JSON_PRETTY_PRINT ) ); - - // file exists? - error_log( "file exists: " . json_encode( file_exists( $image ), JSON_PRETTY_PRINT ) ); - - // log mime type of resized image - error_log( "mime type of resized image: " . json_encode( wp_get_image_mime( $image ), JSON_PRETTY_PRINT ) ); list( $w, $h, $type ) = wp_getimagesize( $image ); - if ( null === $w ) { - // Log the editor type and skip the test. - error_log( '!!!' ); - error_log( "image: " . json_encode( $image, JSON_PRETTY_PRINT ) ); - error_log( "sizes from wp_getimagesize: " . json_encode( wp_getimagesize( $image ), JSON_PRETTY_PRINT ) ); - error_log( "editor: " . json_encode( get_class( $editor ), JSON_PRETTY_PRINT ) ); - error_log( "editor for image after: " . json_encode( get_class( wp_get_image_editor( $image ) ), JSON_PRETTY_PRINT ) ); - error_log( "editor supports avif: " . json_encode( $editor->supports_mime_type( 'image/avif' ), JSON_PRETTY_PRINT ) ); - $this->markTestSkipped( sprintf( '!!! No AVIF support in the editor engine %s on this system.', $this->editor_engine ) ); - } unlink( $image ); From 663a42415d2fc72b412f48560ff416e72ac9d1fa Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Mon, 15 Jan 2024 08:01:07 -0700 Subject: [PATCH 51/79] Update doc blocks, cleanup --- src/wp-includes/class-wp-image-editor-gd.php | 4 +--- src/wp-includes/functions.php | 10 +++++----- src/wp-includes/media.php | 5 ++--- tests/phpunit/tests/image/resize.php | 2 -- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/wp-includes/class-wp-image-editor-gd.php b/src/wp-includes/class-wp-image-editor-gd.php index bc4595c42178f..5edd354e77a28 100644 --- a/src/wp-includes/class-wp-image-editor-gd.php +++ b/src/wp-includes/class-wp-image-editor-gd.php @@ -62,7 +62,6 @@ public static function test( $args = array() ) { */ public static function supports_mime_type( $mime_type ) { $image_types = imagetypes(); - switch ( $mime_type ) { case 'image/jpeg': return ( $image_types & IMG_JPG ) != 0; @@ -579,13 +578,12 @@ public function stream( $mime_type = null ) { header( 'Content-Type: image/webp' ); return imagewebp( $this->image, null, $this->get_quality() ); } - // Fall back to the default if WebP isn't supported. case 'image/avif': if ( function_exists( 'imageavif' ) ) { header( 'Content-Type: image/avif' ); return imageavif( $this->image, null, $this->get_quality() ); } - // Fall back to the default if AVIF isn't supported. + // Fall back to JPEG as the default. default: header( 'Content-Type: image/jpeg' ); return imagejpeg( $this->image, null, $this->get_quality() ); diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index a1be67844342c..ebda593cfab14 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -3296,7 +3296,7 @@ function wp_check_filetype_and_ext( $file, $filename, $mimes = null ) { * * @since 4.7.1 * @since 5.8.0 Added support for WebP images. - * @since 6.4.0 Added support for AVIF images. + * @since 6.5.0 Added support for AVIF images. * * @param string $file Full path to the file. * @return string|false The actual mime type or false if the type cannot be determined. @@ -3359,12 +3359,12 @@ function wp_get_image_mime( $file ) { * */ if ( - // AVIF: lossless, lossy + // AVIF: lossless or lossy. ( str_starts_with( $magic, '0000002066' ) ) || - ( str_starts_with( $magic, '0000001866' ) ) || // magic from tests - // AVIF: animated + ( str_starts_with( $magic, '0000001866' ) ) || // AVIF created by Imagick. + // AVIF: animated. ( str_starts_with( $magic, '0000002c66' ) ) || - // AVIF: transparent + // AVIF: transparent. ( str_starts_with( $magic, '0000001c66' ) ) ) { $mime = 'image/avif'; diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index eb055d267c9eb..ef06475664742 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -5479,7 +5479,7 @@ function wp_show_heic_upload_error( $plupload_settings ) { * * @since 5.7.0 * @since 5.8.0 Added support for WebP images. - * @since 6.4.0 Added support for AVIF images. + * @since 6.5.0 Added support for AVIF images. * * @param string $filename The file path. * @param array $image_info Optional. Extended image information (passed by reference). @@ -5576,7 +5576,7 @@ function wp_getimagesize( $filename, array &$image_info = null ) { /** * Extracts meta information about an AVIF file: width, height, and type. * - * @since 6.4.0 + * @since 6.5.0 * * @param string $filename Path to an AVIF file. * @return array { @@ -5630,7 +5630,6 @@ function wp_get_avif_info( $filename ) { } return $features; - } } diff --git a/tests/phpunit/tests/image/resize.php b/tests/phpunit/tests/image/resize.php index 615c12cb0c47d..094e1d341ea9d 100644 --- a/tests/phpunit/tests/image/resize.php +++ b/tests/phpunit/tests/image/resize.php @@ -92,8 +92,6 @@ public function test_resize_webp() { * Test resizing AVIF image. * * @ticket 51228 - * @ticket 99999 - * */ public function test_resize_avif() { $file = DIR_TESTDATA . '/images/avif-lossy.avif'; From 0b54d590b7d5d97bafa9c8febddd7e477e70a725 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Mon, 15 Jan 2024 16:55:06 -0700 Subject: [PATCH 52/79] add fallback comment --- src/wp-includes/class-wp-image-editor-gd.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wp-includes/class-wp-image-editor-gd.php b/src/wp-includes/class-wp-image-editor-gd.php index 5edd354e77a28..ca6e4969286fc 100644 --- a/src/wp-includes/class-wp-image-editor-gd.php +++ b/src/wp-includes/class-wp-image-editor-gd.php @@ -578,6 +578,7 @@ public function stream( $mime_type = null ) { header( 'Content-Type: image/webp' ); return imagewebp( $this->image, null, $this->get_quality() ); } + // Fall back to JPEG as the default. case 'image/avif': if ( function_exists( 'imageavif' ) ) { header( 'Content-Type: image/avif' ); From dd14f4aae3c7ced8d674fbc4867d8b8a32bd15cf Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Wed, 17 Jan 2024 11:50:57 -0700 Subject: [PATCH 53/79] improve libavifinfo doc block, link to source --- src/wp-includes/class-avif-info.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wp-includes/class-avif-info.php b/src/wp-includes/class-avif-info.php index 84a2e9333de08..beb2de56250e2 100644 --- a/src/wp-includes/class-avif-info.php +++ b/src/wp-includes/class-avif-info.php @@ -8,6 +8,8 @@ * 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. + * + * Note: this class is from libavifinfo - https://aomedia.googlesource.com/libavifinfo/+/refs/heads/main/avifinfo.php */ namespace Avifinfo; From 447a44c057e5ed689becfc84c80f38e740900f7d Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Wed, 17 Jan 2024 12:35:14 -0700 Subject: [PATCH 54/79] remove lossless AVIF image handling: not supported in PHP --- src/wp-includes/class-wp-image-editor-imagick.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/wp-includes/class-wp-image-editor-imagick.php b/src/wp-includes/class-wp-image-editor-imagick.php index ffc3d7ea7761e..2aab4738440cd 100644 --- a/src/wp-includes/class-wp-image-editor-imagick.php +++ b/src/wp-includes/class-wp-image-editor-imagick.php @@ -220,14 +220,6 @@ public function set_quality( $quality = null ) { } break; case 'image/avif': - $avif_info = wp_get_avif_info( $this->file ); - if ( 'lossless' === $avif_info['type'] ) { - // Use quality 100 to trigger AVIF lossless. - $this->image->setImageCompressionQuality( 100 ); - } else { - $this->image->setImageCompressionQuality( $quality ); - } - break; default: $this->image->setImageCompressionQuality( $quality ); } From 6af974c1a949728e19055a63d0196e8919bd71e8 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Wed, 17 Jan 2024 12:35:36 -0700 Subject: [PATCH 55/79] Simplify wp_get_avif_info and improve doc blocks --- src/wp-includes/media.php | 57 +++++++++++++++------------------------ 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index ef06475664742..30b8caf2d89a0 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -5574,7 +5574,7 @@ function wp_getimagesize( $filename, array &$image_info = null ) { } /** - * Extracts meta information about an AVIF file: width, height, and type. + * Extracts meta information about an AVIF file: width, height, bit depth, and number of channels. * * @since 6.5.0 * @@ -5582,9 +5582,10 @@ function wp_getimagesize( $filename, array &$image_info = null ) { * @return array { * An array of AVIF image information. * - * @type int|false $width Image width on success, false on failure. - * @type int|false $height Image height on success, false on failure. - * @type string|false $type The AVIF type: one of 'lossy' or 'lossless'. False on failure. + * @type int|false $width Image width on success, false on failure. + * @type int|false $height Image height on success, false on failure. + * @type int|false $bit_depth Image bit depth on success, false on failure. + * @type int|false $num_channels Image number of channels on success, false on failure. * } */ function wp_get_avif_info( $filename ) { @@ -5596,41 +5597,25 @@ function wp_get_avif_info( $filename ) { return compact( 'width', 'height', 'type' ); } - if ( function_exists( 'avif_get_info' ) ) { - - $avif_info = avif_get_info( $filename ); - - if ( ! $avif_info ) { - return compact( 'width', 'height', 'type' ); - } - - $width = $avif_info['width']; - $height = $avif_info['height']; - $type = $avif_info['type']; - - return compact( 'width', 'height', 'type' ); - } else { - // Fall back to directly parsing the file headers. - require_once ABSPATH . WPINC . '/class-avif-info.php'; - $features = array( - 'width' => false, - 'height' => false, - 'bit_depth' => false, - 'num_channels' => false, - ); + // Parse the file using libavifinfo's PHP implementation. + require_once ABSPATH . WPINC . '/class-avif-info.php'; + $results = array( + 'width' => false, + 'height' => false, + 'bit_depth' => false, + 'num_channels' => false, + ); - $handle = fopen( $filename, 'rb' ); - if ( $handle ) { - $parser = new Avifinfo\Parser( $handle ); - $success = $parser->parse_ftyp() && $parser->parse_file(); - fclose( $handle ); - if ( $success ) { - $features = $parser->features->primary_item_features; - } + $handle = fopen( $filename, 'rb' ); + if ( $handle ) { + $parser = new Avifinfo\Parser( $handle ); + $success = $parser->parse_ftyp() && $parser->parse_file(); + fclose( $handle ); + if ( $success ) { + $results = $parser->features->primary_item_features; } - - return $features; } + return $results; } /** From c5820c3a56d13faf9ac2ba7a161ae68716cdf1e0 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Wed, 17 Jan 2024 12:52:39 -0700 Subject: [PATCH 56/79] Add todo for detection change --- src/wp-includes/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index beca1b55477bb..ca938c5bcab61 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -3355,7 +3355,7 @@ function wp_get_image_mime( $file ) { /** * Add AVIF fallback detection when image library doesn't support AVIF. * - * Note: detection values come from libavif and test files. + * @todo check the third 4-byte token "[major_brand]", for "avif" or "avis" . * */ if ( From c0267710f74717ed29c278ba1ae1c72fabab3a27 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 18 Jan 2024 10:32:57 -0700 Subject: [PATCH 57/79] WP_Image_Editor_GD:stream - improve mime detection and fallback --- src/wp-includes/class-wp-image-editor-gd.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/class-wp-image-editor-gd.php b/src/wp-includes/class-wp-image-editor-gd.php index ca6e4969286fc..23331c7f196cc 100644 --- a/src/wp-includes/class-wp-image-editor-gd.php +++ b/src/wp-includes/class-wp-image-editor-gd.php @@ -577,14 +577,17 @@ public function stream( $mime_type = null ) { if ( function_exists( 'imagewebp' ) ) { header( 'Content-Type: image/webp' ); return imagewebp( $this->image, null, $this->get_quality() ); + } else { + // Fall back to JPEG. + header( 'Content-Type: image/jpeg' ); + return imagejpeg( $this->image, null, $this->get_quality() ); } - // Fall back to JPEG as the default. case 'image/avif': if ( function_exists( 'imageavif' ) ) { header( 'Content-Type: image/avif' ); return imageavif( $this->image, null, $this->get_quality() ); } - // Fall back to JPEG as the default. + // Fall back to JPEG. default: header( 'Content-Type: image/jpeg' ); return imagejpeg( $this->image, null, $this->get_quality() ); From ddbd3440f50bc3dd06bd98c8a4cd4601a60558df Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 18 Jan 2024 11:12:06 -0700 Subject: [PATCH 58/79] WP_Image_Editor_Imagick::update_size - improve logic for avif fallback --- src/wp-includes/class-wp-image-editor-imagick.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-image-editor-imagick.php b/src/wp-includes/class-wp-image-editor-imagick.php index 2aab4738440cd..02e3d204d7ab6 100644 --- a/src/wp-includes/class-wp-image-editor-imagick.php +++ b/src/wp-includes/class-wp-image-editor-imagick.php @@ -259,7 +259,7 @@ protected function update_size( $width = null, $height = null ) { // If we still don't have the image size, fall back to `wp_getimagesize`. This ensures AVIF images // are properly sized without affecting previous `getImageGeometry` behavior. - if ( ! $width && ! $height && 'image/avif' === $this->mime_type ) { + if ( ( ! $width || ! $height ) && 'image/avif' === $this->mime_type ) { $size = wp_getimagesize( $this->file ); $width = $size[0]; $height = $size[1]; From 3ecb6825eeb5a8b224a115a07a19906fd457270a Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 18 Jan 2024 11:36:57 -0700 Subject: [PATCH 59/79] Update header based AVIF detection --- src/wp-includes/functions.php | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index ca938c5bcab61..1ac278eae093b 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -3331,9 +3331,9 @@ function wp_get_image_mime( $file ) { return $mime; } - $magic = file_get_contents( $file, false, null, 0, 12 ); + $header = file_get_contents( $file, false, null, 0, 12 ); - if ( false === $magic ) { + if ( false === $header ) { return false; } @@ -3342,7 +3342,7 @@ function wp_get_image_mime( $file ) { * Note: detection values come from LibWebP, see * https://github.com/webmproject/libwebp/blob/master/imageio/image_dec.c#L30 */ - $magic = bin2hex( $magic ); + $magic = bin2hex( $header ); if ( // RIFF. ( str_starts_with( $magic, '52494646' ) ) && @@ -3358,14 +3358,15 @@ function wp_get_image_mime( $file ) { * @todo check the third 4-byte token "[major_brand]", for "avif" or "avis" . * */ + + // Divide the header string into 4 byte groups. + $magic = str_split( $magic, 8 ); + if ( - // AVIF: lossless or lossy. - ( str_starts_with( $magic, '0000002066' ) ) || - ( str_starts_with( $magic, '0000001866' ) ) || // AVIF created by Imagick. - // AVIF: animated. - ( str_starts_with( $magic, '0000002c66' ) ) || - // AVIF: transparent. - ( str_starts_with( $magic, '0000001c66' ) ) + isset( $magic[1] ) && + isset( $magic[2] ) && + 'ftyp' === hex2bin( $magic[1] ) && + ( 'avif' === hex2bin( $magic[2] ) || 'avis' === hex2bin( $magic[2] ) ) ) { $mime = 'image/avif'; } From b6b78d019283ad77dd57665aee2f8d00acf7f0af Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 18 Jan 2024 11:56:09 -0700 Subject: [PATCH 60/79] Add commit hash to libavifinfo reference --- src/wp-includes/class-avif-info.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/class-avif-info.php b/src/wp-includes/class-avif-info.php index beb2de56250e2..72405c326c69b 100644 --- a/src/wp-includes/class-avif-info.php +++ b/src/wp-includes/class-avif-info.php @@ -9,7 +9,7 @@ * 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. * - * Note: this class is from libavifinfo - https://aomedia.googlesource.com/libavifinfo/+/refs/heads/main/avifinfo.php + * Note: this class is from libavifinfo - https://aomedia.googlesource.com/libavifinfo/+/refs/heads/main/avifinfo.php at b496868. */ namespace Avifinfo; From 8c51a64416d15f559e573e9a6f84bed8a16fa3fa Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 18 Jan 2024 11:59:42 -0700 Subject: [PATCH 61/79] revert creation of additional header variable --- src/wp-includes/functions.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 1ac278eae093b..e5a39f588b473 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -3331,9 +3331,9 @@ function wp_get_image_mime( $file ) { return $mime; } - $header = file_get_contents( $file, false, null, 0, 12 ); + $magic = file_get_contents( $file, false, null, 0, 12 ); - if ( false === $header ) { + if ( false === $magic ) { return false; } @@ -3342,7 +3342,7 @@ function wp_get_image_mime( $file ) { * Note: detection values come from LibWebP, see * https://github.com/webmproject/libwebp/blob/master/imageio/image_dec.c#L30 */ - $magic = bin2hex( $header ); + $magic = bin2hex( $magic ); if ( // RIFF. ( str_starts_with( $magic, '52494646' ) ) && From 478e5d510dcc1cdac0a42a7021ecaf5093c2219b Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 18 Jan 2024 12:35:47 -0700 Subject: [PATCH 62/79] Doc block cleanup. --- src/wp-includes/media.php | 17 ++++++++--------- tests/phpunit/tests/image/resize.php | 1 - 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index a2cb6ead8430e..48317dfff8053 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -5521,7 +5521,7 @@ function wp_getimagesize( $filename, array &$image_info = null ) { if ( ! empty( $info ) && - // Some PHP versions return 0x0 sizes from `getimagesize` for unrecognized image formats, including AVIFs + // Some PHP versions return 0x0 sizes from `getimagesize` for unrecognized image formats, including AVIFs. ! ( empty( $info[0] ) && empty( $info[1] ) ) ) { return $info; @@ -5552,13 +5552,12 @@ function wp_getimagesize( $filename, array &$image_info = null ) { } } - // For PHP versions that don't support AVIF images, - // extract the image size info from the file headers. + // For PHP versions that don't support AVIF images, extract the image size info from the file headers. if ( 'image/avif' === wp_get_image_mime( $filename ) ) { $avif_info = wp_get_avif_info( $filename ); - $width = $avif_info['width']; - $height = $avif_info['height']; + $width = $avif_info['width']; + $height = $avif_info['height']; // Mimic the native return format. if ( $width && $height ) { @@ -5589,10 +5588,10 @@ function wp_getimagesize( $filename, array &$image_info = null ) { * @return array { * An array of AVIF image information. * - * @type int|false $width Image width on success, false on failure. - * @type int|false $height Image height on success, false on failure. - * @type int|false $bit_depth Image bit depth on success, false on failure. - * @type int|false $num_channels Image number of channels on success, false on failure. + * @type int|false $width Image width on success, false on failure. + * @type int|false $height Image height on success, false on failure. + * @type int|false $bit_depth Image bit depth on success, false on failure. + * @type int|false $num_channels Image number of channels on success, false on failure. * } */ function wp_get_avif_info( $filename ) { diff --git a/tests/phpunit/tests/image/resize.php b/tests/phpunit/tests/image/resize.php index 094e1d341ea9d..e82dd3d2e6b2a 100644 --- a/tests/phpunit/tests/image/resize.php +++ b/tests/phpunit/tests/image/resize.php @@ -114,7 +114,6 @@ public function test_resize_avif() { $this->assertSame( IMAGETYPE_AVIF, $type ); } - public function test_resize_larger() { // image_resize() should refuse to make an image larger. $image = $this->resize_helper( DIR_TESTDATA . '/images/test-image.jpg', 100, 100 ); From c77399048a09d871719bec4a39b695ba531d70ac Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 19 Jan 2024 08:23:00 -0700 Subject: [PATCH 63/79] replace avif-transparent test image --- .../phpunit/data/images/avif-transparent.avif | Bin 5378 -> 719 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/phpunit/data/images/avif-transparent.avif b/tests/phpunit/data/images/avif-transparent.avif index db34cd3f74c98b614e05b0095e1d7d37bccb96e8..8165f9ff46a24bf8cd28ac93c2e3edd140a642cd 100644 GIT binary patch delta 556 zcmZqDI?p;mo_9uWYDppk1Sn3_k*NnU`3f>ia=~JZK++^LCqEg)b8%o`0zx2Q+`|Bp z0f90Ii^&KsG8ZUnk(rs729^h@7X@-sAsQJN63Yw>K{5zIpbSj#x^`wkQ2|hPV`f2e zK9Ckp&d({zOU|hPTg}!06yixNGjwirs9~5~&Zs(hGouA3R3wLCvJRtqyQ)1Q4ga_1H5hx_DpSz4^LgwQEh9`tlaPS`$sZmAj(mwQ64% S;b}Oxsw`>k46$9sSq%W_N3|UQ literal 5378 zcma)8cQl;e)*g%!L9`%<-l9eqorxA*LP)e3Z6*w35Tdt4uM?dJqL(O9q7%KhQ6fro zQ4+llcjWiob?^P}+v~h%KhNIJ*=N7&UF)0|001!By1qexU&CwxSiA9Xn5{4z2F7xn z3%HFd_$H1ub*Qxi@*fBQAYfLm|He1o>SiH}01MRB72zT+BxDD3g}Olmt(@RO4lqZ1 z0XLz)iW@0E%)!a(1^^WSxB#3Rgv)&6fu2~7HG-@gMs~CBV;IcQ_OAph!g6{nwfQT; z2Enf%3Twc@b~ZPQ{*%F`|0hF+fVskNB!Bl}f+21GU6B&(=HY;i|I-DwBF=3X0*Syz zCSV9FC#)w&fL(6*O$#>d!43#0_(o0(L%{z6EXD!g0C8}Gg0Yb9f5N!<_yi%LVc3rm z2)72i{?{we6H@yVu~gzKvMm#xkth(K$W9_~#8S!dU+Z=*m8<>MC+N_lH~4hdKRUbR zg^qzWdcx1~HIlC9E65%iE*qU96-ldNdOhh+Mc6KJms!p8rR!>KBWriwV2#8iG@@6v zTy=9yliEnlgu+N!sB)sq2g%L@{90E09n}fUxYiiCa77x=gLfE~J#s>U&}(nw(6ujV{lvM!M*TNlbY7?d0zRYZ==lu2Yo`O1CTl8DWmMIeyQmanhF_thus5c(tT{`5f)C z6zLI%MdKH_D+8Ok@XtWvc^8}#lfR;NWY?oRT2b8-TC(xrooNYQjz5u)5N74j94=c| zH6tV6FzKM9}bNT)f8&v!e{I3 zN)o5fwJ~)v0|J?87+$I|-ob35^$up`Bl(7|(S%cYx zkntbrI`@p;e^Yy9`BcL1-RdT6+s^P~#b&zK2uanB+tM6Q$-yG;34K*=lvi+J_I+TJ7IO7JiNlnvZ+t?me7=Af{be!;mZ!v+WQYa#9RANe3?Be^b^tq7Q*H7lG^5&TlJ;HfU2DLcx z=_A5FDziL3yF~Su&lu6sIgN}h-ddG-Q6k+t8nY2gHI}XnZ#-f)h2Yah8oqWm@)`Q5 z8+bRK{CDq{>j+9qa>f&ps#)_xFK`8`W1Z~qWLcCNB~MI+fjNgQ-s zej%sREOCTEchci;8{L9PN`E&%MN{UWwYht3nwnLa&hu03K|35kPXF9nb>EsKUVl!` z%SpqL%8);33mU+v?mzX!sFBE!Jc`DpEOd%$1-3`jHHucmaM|lH%GU06b=F*WMbv9I z)$vwR_)FX_Pi0XW+5}h(<_KNo{<_A&(RxNs3#b0(_4Dl2&&wnY&HAA1kL(8L)(7!~ ztm@Th=RQ7(r0@Mk0lbb}Q?Jt|%Ce=qzIcbFQ*KeW-p!w6dLKiIhgnBwtI4jui+F@- zafqj~Pe-N9w>TlWaz`ZzL;q~>{@Ea1a5ip6t9RH&cd6NX2BH9!g^sFvi5e9N_Eq+; z$MmhA3qz9C-O9*Q4U>j1*25fR@*V77jCq7K0NKyB`o)kQd26vjKToyB8yA$09_odu z-SxCFaCJn9oMo#i@p-#jA>zg?DX0dIp0c}U^`0OveUGfc9>l@bcqhR&fizugM_pMx zVRP6E0QO%e(9PLLk4FyhXB%QXKBipr;jh1idUkz%eL25J2hG!7?$Fa%J#4WW2v2x= ziL&tQ%VQ^)uSD~c=#y?&ysz+Bw7)(JSO#gWJ?^h!5=(wTt-iT_d+5URfqhst4tSQA z&nfE4J@tymJcg;ok}NlniDMj}(4zLklwac)?l84dm*KKYWGsXj-tyz;lTo$s? ze%~XYWILFC;Il>0`+n*j)=w4wUTrnn?sqbI&o4Ri6ZNl1q__ayr{iyH1K6Kit|jK` zRmy++q&OsxuFL`{XqI<2Fsw8u{bqT4{%YMH)ve*>VB3AShhBa2g9_!8utUo!1xtj& z)IibKS|zQI22yEFt8EFYoXPK=%Z+^U;PQ;2tuFjU2p1Gsa83l@)X&~Zco*TGdM!ftW=uU6xP01y8I<&iOq@aX*4q$amHmg zz|i#`9NvJ}q8F9HBw$|D$I(V2MhbL)?1=hR(mA-JBzc_lV}fy^P(z7M(|EkfcF;tW zdKASs$ROb`Q)Q%;B4XWlpgQ3;HT`9>Luyfbm`7BMiLD#gqyotoX|>ix|8 z&2!(6_eSya>txNtaGcy#n6{|ytWXB*&dkNkWx}0H5=a7{=?xI0tiB(7rhlv4fD3*?}=rc*$1~Dg)12$7!(GpBlhmy2MOUXabe>TeK${H*FV~dAVCJ&zZBi zka{RrNw8K`*3?G^A9UZ&hSM+((NGJNTUijHLJosm)MUMpw+`Lt7U$DDEAySwQH_@^ z=FwaI>_ysc2n#;Z)iyZ1ZIC^@7_Abk%x_cs`>7e%Bz-2GRwf)FE?Wa4+Lv!DC&0o9yrpI4TGJw3D z5SR34uBIh)YN%J5`$_1&af|XPt=X3t*C*B*exR_!`LgZq-C`h~z)USPc zcIE&yro~vweW5O1?4k3gBhT%%WR7s^5_{JBcdA<*1Eh2rfoDwCDkm7SrY2dCPzrDR z6zTrK1Zk50MZR6{Yz~JU53!;3gk!{MmFWQ@l@*W9hV0&Fo|&Y zR@Y9Lmh!-nenR6P{(yOj*eKuGw8v&m$uVg<0;PqPSB@5I;}J;bxL(!}eSKgyey-Cb zhL7*Sm%g@INVF%cjc;FswB%2P$wCsd<4{(8wmqhHC*(}!R7PeMl)E+_ z820lRgtnO=LZ92#HmM-hPkBO{pCBU{swbm_TPCWLJ|nU3)T0jo0lp0-^)RjQ+5Yi) zfMPIgq)yUFj}OW4mZ&xy;b|$uq@6Hjrv+jrglUo2$Fw?dooh_XHofJ1Av-Q7ZZ?8u zSsp|K7m`Ocu81A(lnXT^hatCT@Ag-iHas7`nYxiX7W(5k0B)(r4WC)VS;9(1`z!T6 z4CxuSLFOTItHj_+qR@=l<3xdRr&+IZ`4JMKMNZRk z4kBSuk|2}$qSVy&azVV}Ze=A8M{tG6kqpnTkirK>@suNS8b*Y^1xNM?cpJ!W3P<&g zi~ai&foJ#h8ChAvR@#FuzfWIX$u8^^TAe_75O+eTO2>EI%h{TqF88>@b6w>3#xL2}K$St8L9*DgD@tqECQ*XKvexTj ztL(?Q*(|6&k37RkGGx)ui(6(Qmn&uEMB$qayZA_tTZgSXeP-lGiNOR)6cPF}e-g9O z&oyt>$MqhWTQ$%#fh)`4A0Jn4^^CbNLP(M$*pE6jqAk?TCJgpw`A{?O12R&d)9!jbglwuEoNR(0L#S0*llAFBDxN`@ zy0W$n89~Luf^C%DvkzXb3fYC0CIIU?s>RDnPD2q9eZ45v2#Kl{B)~H%0RJ8CkYKiE zfzPBGt_^xVQ(Z9YY+Vu=smiZKy=pm-(Whn7(cOIe_ofzmKWM!txU=024siPTV+Fg7{TE}!mGe%man4p*-r4!4sC^R@x z7=K8sEmU@&2s`Ac`UOh)IiSg$#*4Ir%#VV%?h8x@|77$KdH;CcId+A(1QW27b-Qq9 zy4-utZgkOMJnQ@|=R+j)3>eso*D3}{c_0~g6oe!G7Hr=pu|N(8XU8SZN#1$7vh_-I zP4SkGoO^OyovK+ZPD*5l0__5iCbfGeAbsVv^H5dXE#lE%W;>8!B)+yzPk6?4` zsN+}1ih^A>1xICT8w`IUEphX616J2A299@9(aO+dxSOiCa5P82cwEm5kM!wxLdtR* z`b6|m zqRzyH7ZdPztgzmOD_IERDc8Zywr*{0vk3=@egklJHKwHj_4zW`C|R!SV8yAO^hn`{ zJp6)i-^emImlTV_&%%QFjz`^R zL~G%reH`S~-V7cgo-Qpxs&UVek+{WR6+G9u*bc7519q{M4{+NJdO>t9!@JmCvW0Qt z#zGIjy($h9KD{DQje$;zehPFee^cLGv!mQzm!)N&WKMG+jSbR)Q8s~T%X4H&TY1A= z-@C~!CBHNeuHZ(y`-dbR^QqpbJe(TJL70z20GAIgAv zn+`%{MSdj1~=7BsnnR1#cM4Bt!Oi0-kw9yS8qj*kGPoDGWAyp#i z6N%B8w^IuSqZF=p{*0O0ydVkc-}!L_ERLHHp(+^Eq@i=9&Tf`=GqW5T9~MJX9RIPw zy=dmHUi2g3%Xuf#Te*HNwh1o z_S;zcT?BLqMz-#Oq>^?0@j6@l^i>S9<}PArFc{s{kMIS!B131%>ovhoef!s%u&rz{Ydk5Nnx!EB2u$ulUkC( z$-RHT0+CvhJa{*Isn9be1rG__Q^F@280I%r>(9Ua0Y-VG-@YEd+P)51&W6f$2pbap z5>vZ?ML`ngh6M#)_D4J`cofx^tfM+N*;HPED;U{ai_D(c1vUG84Tzp`$kSQF;lg2& z(|M?=jgCS`r=|&TkDqXEdKw;L6m)+iCVlWcPi1 ztIy)2$9q+zM&6rBh9P3@Il98iXmyLH#|8;VEAW$q6kV4@^|97ha<$V>wTConI>st$ z?)17H27FuoUXVH#=xu*u_@np7P;5&n592F}-4lVLToJT28C@;STp4{Tg88e$JapK{ z$%7qzvJmc1L`tH+Y)DKYN3LEOx5`_4KjDkBy6fZo_}%E~iKwm@6ANP`g8U#F5uy^j zH%J}#wE6qm)&2HeU%Iw6k$6M2=P=(nmdQrLPuT9Zpe2UlL L_-8D&-ctM*x5@z@ From b2ec77831914e7716d54d0b7ece3e9ab7004e873 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 19 Jan 2024 08:31:02 -0700 Subject: [PATCH 64/79] Replace @todo comment with link to specification --- src/wp-includes/functions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index e5a39f588b473..34c63094d8d3f 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -3355,8 +3355,8 @@ function wp_get_image_mime( $file ) { /** * Add AVIF fallback detection when image library doesn't support AVIF. * - * @todo check the third 4-byte token "[major_brand]", for "avif" or "avis" . - * + * Detection based on section 4.3.1 File-type box Definition of the ISO/IEC 14496-12 + * specification, https://see aomediacodec.github.io/av1-avif/v1.1.0.html#brands. */ // Divide the header string into 4 byte groups. From 5905869ff9da54c0659bfdbde0e564f4e2286000 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 19 Jan 2024 09:16:37 -0700 Subject: [PATCH 65/79] Always test `wp_get_avif_info` regardless of server AVIF support --- tests/phpunit/tests/image/editor.php | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/tests/phpunit/tests/image/editor.php b/tests/phpunit/tests/image/editor.php index 1244e761cf749..bff477f159f0b 100644 --- a/tests/phpunit/tests/image/editor.php +++ b/tests/phpunit/tests/image/editor.php @@ -372,12 +372,6 @@ public function data_wp_get_webp_info() { * */ public function test_wp_get_avif_info( $file, $expected ) { - $editor = wp_get_image_editor( $file ); - - if ( is_wp_error( $editor ) || ! $editor->supports_mime_type( 'image/avif' ) ) { - $this->markTestSkipped( sprintf( 'No AVIF support in the editor engine %s on this system.', $this->editor_engine ) ); - } - $file_data = wp_get_avif_info( $file ); $this->assertSame( $expected, $file_data ); } @@ -445,6 +439,24 @@ public function data_wp_get_avif_info() { 'num_channels' => 3, ), ), + array( + DIR_TESTDATA . '/images/color_grid_alpha_nogrid.avif', + array( + 'width' => 80, + 'height' => 80, + 'bit_depth' => 8, + 'num_channels' => 3, + ), + ), + array( + DIR_TESTDATA . '/images/colors_hdr_p3.avif', + array( + 'width' => 200, + 'height' => 200, + 'bit_depth' => 10, + 'num_channels' => 3, + ), + ), ); } } From 2cb8cae5979111bbe276017da71eb84d107fbf20 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 19 Jan 2024 09:17:09 -0700 Subject: [PATCH 66/79] Add additional AVIF test images, remove one unused image --- tests/phpunit/data/images/avif-rotated.avif | Bin 84837 -> 0 bytes .../data/images/color_grid_alpha_nogrid.avif | Bin 0 -> 2373 bytes tests/phpunit/data/images/colors_hdr_p3.avif | Bin 0 -> 26532 bytes 3 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/phpunit/data/images/avif-rotated.avif create mode 100644 tests/phpunit/data/images/color_grid_alpha_nogrid.avif create mode 100644 tests/phpunit/data/images/colors_hdr_p3.avif diff --git a/tests/phpunit/data/images/avif-rotated.avif b/tests/phpunit/data/images/avif-rotated.avif deleted file mode 100644 index ee7c5246e13054a81f70f4affea1571381a88474..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 84837 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 diff --git a/tests/phpunit/data/images/color_grid_alpha_nogrid.avif b/tests/phpunit/data/images/color_grid_alpha_nogrid.avif new file mode 100644 index 0000000000000000000000000000000000000000..fa301f589822c17bd32a372ca4569bdc492f3e71 GIT binary patch literal 2373 zcmZ`&2|Scr8-LAW%~-|~iHSRueJ3heV!E2hk~L!t!>bu4GuB+&<&sd@K2i!<3fT=2 zQ6I{BODSb+Sz1t*65_tY=l6a0`+eVef9L$4=lMV9f1dN6a{vIqkQt#gVh~6MpvFcj zNYM%Nl+yHFHLpA{boF9Pic-&nO{s!glWL|`_@c`$JhhTF{P`?U-e%m!~ z=Sf127h8<2?HvGmn)*}xw@ck+IM{0ugVZ(t2LrA9e;B9+He*bo`4HLa|5k(Q`5U7T z26*k%0|0`aAk`bH1O|QVJ)ww!A(ZXCvrF9ZI3b?ReFA9#GzhCe8p$6*5s*&vf{@*; z|J4O)UYN(iLE4{%07q5qzC9E$BB25Fc^V;~D!bXS6dEuaq- zWXcW)2KX6JX&29m2Lp#fE17z5Nbf&-TsauAjO`l3DT(B{{*t3 zV}*0T-~bGPfW^T9_zoZu2+jm9fO7``&S9!25xO$a30VWy!T>MlUJ38b_S>3|5P|?O zqNJERsIRMwlXZB*JElI{!pQ}LP|3$8IRF^g@c$EE!U z$-!+h9Zox;WA8=Nd1^!Y{I|ANZQL1ylfxeKts94ijyKQfOexL08_=h__eK^`>y~N&*^AbLK4if~!29O%bIYT%(U&$~ z*tHN9#{DL*DfOP|L5AKl%KN0ygv*=7CTmaEAuG*RSxnn0KMt*5tl!-~AI4?(-Rt18 z3-d^7m05}wCcE2?NO4LV%SoTJQ0Reo+LG(~oXr)Z2T~7Ox{lteJs?&S!Z~;@{=$() z9i_?Gl#_4J$A*2>`bE_5yP2??#_vsyyuR)6K1Rls-S7d(b%qneER?88y2wO8(Nii*Ymct6%8n;=~`iL*!kp1&AA;Ne=f_;li1bbJ4U6Xv5s*PkWHtoU_V&p01`8%_Z~ zyUG{4zMV?v+v>dWoVLc-CSshq*PPc3YfA8Y`{Jw0<+PxBg<)~l(n{WQnM)f%pPiY~ zljQ}Oa2N5+L4=8AO1|anJ4AY;2mky~`3p}U@%9kldykW#HLUkw*KvsLydgKRVgA%v1syj#Xv1y ztEb7f8tsdkq_2OB+8ZvIEfz+y>^7MyF+#dey^DL+lR3E$rSOQVD(~wmm2BGY(VcP5 zh*qjI(k1`pP4+?hjLpMf)yfa!wX)0Woo;p-Ys&huh0*mn8g~qBpUh`VpVhT0inQC8 zIm6p1t$fc1BQH{1SLW{=;V^7z)=9-nvEK8gBqZWO-sJMB6!l~d9X+OpO%_%cxFYkj zDD8&{wgDq>EBM1wTh+Qyxl~w88|+aVr!MU9{eEPe$-p5XwM?@&k5|8RP2=xdT9L0Vrt~Qbi%a{cbae4A4RK@N+EP{x*5|br)tu`q(hJ0#j>MZVi=qUPhJ3UDYwSN}&1X(y`Plc5@S$iGebFcE5mRS8A)i*WC#f z-{een^GCHf09{I_m6xn<#&vVJju%~r3Oo>~~ioM}V}y{Zed zJn`5;UlsS7Tc#n1nE1!|0^#-f=r)3lSOVQ9Vj`!c*ih8%=*Puu=btkrhpHkoJ}8rn zboE8ACN8Z>M~RNTS>zLFvCY*PoL`bIE?vFso(v7Y>ES9A5R*Cc4!66 z7Yf`D%53W>#iIj9tS$;;FGZ)KM+BEi$mrB2g&GF!2SvjrMMa|N<;SL^o5z_yN53Pq zb32&x-Vrb~MVQ@eIaVq@c>d>FzFm~Yvb^Hm+E+<$4kcw^bLEMC9rX+NDfjSrGyxK4G)eui;Y8w~YJC3>xt-Wo}}WtrJ2w zN+z;ADh^aU#;e<>8>bwb^F%E(j3qA1b}ToyHfy>6{W19Mz6JB?&0+=djqnr|%yQ%P zsJoq9o|RLUawAKO%>!e p8|IJR^a}o}KIr)2h-o0p=%bvTvuFTpQj`xn<0yr}>H literal 0 HcmV?d00001 diff --git a/tests/phpunit/data/images/colors_hdr_p3.avif b/tests/phpunit/data/images/colors_hdr_p3.avif new file mode 100644 index 0000000000000000000000000000000000000000..6a2403f110da57369d57413490d309f4670ff17f GIT binary patch literal 26532 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 literal 0 HcmV?d00001 From 79587bd8d68c234555dad63a6173cc88ba45fb09 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 19 Jan 2024 09:23:06 -0700 Subject: [PATCH 67/79] Always test wp_get_webp_info regardless of server support for webp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit this function is designed to work regardless of server support for WebP so let’s test it that way --- tests/phpunit/tests/image/editor.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/phpunit/tests/image/editor.php b/tests/phpunit/tests/image/editor.php index bff477f159f0b..fff277174a7fa 100644 --- a/tests/phpunit/tests/image/editor.php +++ b/tests/phpunit/tests/image/editor.php @@ -292,12 +292,6 @@ public function test_get_suffix() { * */ public function test_wp_get_webp_info( $file, $expected ) { - $editor = wp_get_image_editor( $file ); - - if ( is_wp_error( $editor ) || ! $editor->supports_mime_type( 'image/webp' ) ) { - $this->markTestSkipped( sprintf( 'No WebP support in the editor engine %s on this system.', $this->editor_engine ) ); - } - $file_data = wp_get_webp_info( $file ); $this->assertSame( $expected, $file_data ); } From b7c193ca55470bc50eac13ee3f05c7d17ead0d87 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 19 Jan 2024 09:35:57 -0700 Subject: [PATCH 68/79] Update avif-info doc block to indicate it will no longer be required after PHP 8.2 is the minimum version for WordPress --- src/wp-includes/class-avif-info.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/wp-includes/class-avif-info.php b/src/wp-includes/class-avif-info.php index 72405c326c69b..d349ff8a2003c 100644 --- a/src/wp-includes/class-avif-info.php +++ b/src/wp-includes/class-avif-info.php @@ -10,6 +10,10 @@ * PATENTS file, you can obtain it at www.aomedia.org/license/patent. * * Note: this class is from libavifinfo - https://aomedia.googlesource.com/libavifinfo/+/refs/heads/main/avifinfo.php at b496868. + * It is used as a fallback to parse AVIF files when the server doesn't support AVIF, + * primarily to identify the width and height of the image. + * + * Note PHP 8.2 added native support for AVIF, so this class can be removed when WordPress requires PHP 8.2. */ namespace Avifinfo; From 471f77c0b9d5e2781d6236228599e2d25aaaafdb Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Tue, 23 Jan 2024 09:08:36 -0700 Subject: [PATCH 69/79] Correct bit_depth detection bug in avif-info class, adjust tests to match --- src/wp-includes/class-avif-info.php | 4 ++-- tests/phpunit/tests/image/editor.php | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/class-avif-info.php b/src/wp-includes/class-avif-info.php index d349ff8a2003c..93280b3806dad 100644 --- a/src/wp-includes/class-avif-info.php +++ b/src/wp-includes/class-avif-info.php @@ -9,7 +9,7 @@ * 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. * - * Note: this class is from libavifinfo - https://aomedia.googlesource.com/libavifinfo/+/refs/heads/main/avifinfo.php at b496868. + * Note: this class is from libavifinfo - https://aomedia.googlesource.com/libavifinfo/+/refs/heads/main/avifinfo.php at f509487. * It is used as a fallback to parse AVIF files when the server doesn't support AVIF, * primarily to identify the width and height of the image. * @@ -450,7 +450,7 @@ private function parse_ipco( $num_remaining_bytes ) { } else if ( $box->type == 'auxC' ) { // See AV1 Image File Format (AVIF) 4 // at https://aomediacodec.github.io/av1-avif/#auxiliary-images - $kAlphaStr = "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"; + $kAlphaStr = "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha\0"; $kAlphaStrLength = 44; // Includes terminating character. if ( $box->content_size >= $kAlphaStrLength ) { if ( !( $data = read( $this->handle, $kAlphaStrLength ) ) ) { diff --git a/tests/phpunit/tests/image/editor.php b/tests/phpunit/tests/image/editor.php index fff277174a7fa..912bf7062f0d6 100644 --- a/tests/phpunit/tests/image/editor.php +++ b/tests/phpunit/tests/image/editor.php @@ -400,7 +400,7 @@ public function data_wp_get_avif_info() { 'width' => 150, 'height' => 150, 'bit_depth' => 8, - 'num_channels' => 3, + 'num_channels' => 4, ), ), // Lossless AVIF. @@ -430,7 +430,7 @@ public function data_wp_get_avif_info() { 'width' => 128, 'height' => 128, 'bit_depth' => 12, - 'num_channels' => 3, + 'num_channels' => 4, ), ), array( @@ -439,7 +439,7 @@ public function data_wp_get_avif_info() { 'width' => 80, 'height' => 80, 'bit_depth' => 8, - 'num_channels' => 3, + 'num_channels' => 4, ), ), array( From 49f4278b61cb49b678d5a6f69a29ede2db52e62b Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Wed, 24 Jan 2024 09:39:26 -0700 Subject: [PATCH 70/79] Update src/wp-includes/media.php Co-authored-by: Mukesh Panchal --- src/wp-includes/media.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 48317dfff8053..8dcf8322324bf 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -5595,10 +5595,6 @@ function wp_getimagesize( $filename, array &$image_info = null ) { * } */ function wp_get_avif_info( $filename ) { - $width = false; - $height = false; - $type = false; - if ( 'image/avif' !== wp_get_image_mime( $filename ) ) { return compact( 'width', 'height', 'type' ); } From 720910bddafcfd93adfafefafd68a6a42d7b940b Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Wed, 24 Jan 2024 09:39:58 -0700 Subject: [PATCH 71/79] Update tests/phpunit/tests/image/editor.php Co-authored-by: Mukesh Panchal --- tests/phpunit/tests/image/editor.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/phpunit/tests/image/editor.php b/tests/phpunit/tests/image/editor.php index 912bf7062f0d6..c2be1563808b3 100644 --- a/tests/phpunit/tests/image/editor.php +++ b/tests/phpunit/tests/image/editor.php @@ -362,8 +362,11 @@ public function data_wp_get_webp_info() { * Test wp_get_avif_info. * * @ticket 51228 + * * @dataProvider data_wp_get_avif_info * + * @param string $file The path to the AVIF file for testing. + * @param array $expected The expected AVIF file information. */ public function test_wp_get_avif_info( $file, $expected ) { $file_data = wp_get_avif_info( $file ); From 609d875a66934ca0665644691b70392e88df761d Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Wed, 24 Jan 2024 09:43:13 -0700 Subject: [PATCH 72/79] Update doc block for AVIF mime detection --- src/wp-includes/functions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 34c63094d8d3f..41c6aeb54ff6b 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -3355,8 +3355,8 @@ function wp_get_image_mime( $file ) { /** * Add AVIF fallback detection when image library doesn't support AVIF. * - * Detection based on section 4.3.1 File-type box Definition of the ISO/IEC 14496-12 - * specification, https://see aomediacodec.github.io/av1-avif/v1.1.0.html#brands. + * Detection based on section 4.3.1 File-type box definition of the ISO/IEC 14496-12 + * specification and the AV1-AVIF spec, see https://aomediacodec.github.io/av1-avif/v1.1.0.html#brands. */ // Divide the header string into 4 byte groups. From b492dbff3a256103b6c7b56387f4587109eb6d4e Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Wed, 24 Jan 2024 11:38:44 -0700 Subject: [PATCH 73/79] Clean up return from `wp_get_avif_info` --- src/wp-includes/media.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 8dcf8322324bf..353810c334e62 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -5595,12 +5595,6 @@ function wp_getimagesize( $filename, array &$image_info = null ) { * } */ function wp_get_avif_info( $filename ) { - if ( 'image/avif' !== wp_get_image_mime( $filename ) ) { - return compact( 'width', 'height', 'type' ); - } - - // Parse the file using libavifinfo's PHP implementation. - require_once ABSPATH . WPINC . '/class-avif-info.php'; $results = array( 'width' => false, 'height' => false, @@ -5608,6 +5602,13 @@ function wp_get_avif_info( $filename ) { 'num_channels' => false, ); + if ( 'image/avif' !== wp_get_image_mime( $filename ) ) { + return $results; + } + + // Parse the file using libavifinfo's PHP implementation. + require_once ABSPATH . WPINC . '/class-avif-info.php'; + $handle = fopen( $filename, 'rb' ); if ( $handle ) { $parser = new Avifinfo\Parser( $handle ); From 6fe2847dff8f01ca2bb8a8d7b7454fd49d67f617 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Wed, 24 Jan 2024 11:40:00 -0700 Subject: [PATCH 74/79] Correct tests for `wp_get_avif_info` --- tests/phpunit/tests/image/editor.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/phpunit/tests/image/editor.php b/tests/phpunit/tests/image/editor.php index c2be1563808b3..d156ba6756bc8 100644 --- a/tests/phpunit/tests/image/editor.php +++ b/tests/phpunit/tests/image/editor.php @@ -382,18 +382,20 @@ public function data_wp_get_avif_info() { array( DIR_TESTDATA . '/images/test-image.jpg', array( - 'width' => false, - 'height' => false, - 'type' => false, + 'width' => false, + 'height' => false, + 'type' => false, + 'bit_depth' => false, ), ), // Standard GIF. array( DIR_TESTDATA . '/images/test-image.gif', array( - 'width' => false, - 'height' => false, - 'type' => false, + 'width' => false, + 'height' => false, + 'type' => false, + 'bit_depth' => false, ), ), // Animated AVIF. From 942bfbc347aa579bc6872e3cebaa79a2f6c7616f Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Wed, 24 Jan 2024 16:11:43 -0700 Subject: [PATCH 75/79] wp_get_avif_info - test fix for default return --- tests/phpunit/tests/image/editor.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/phpunit/tests/image/editor.php b/tests/phpunit/tests/image/editor.php index d156ba6756bc8..5e857bf47258c 100644 --- a/tests/phpunit/tests/image/editor.php +++ b/tests/phpunit/tests/image/editor.php @@ -382,20 +382,20 @@ public function data_wp_get_avif_info() { array( DIR_TESTDATA . '/images/test-image.jpg', array( - 'width' => false, - 'height' => false, - 'type' => false, - 'bit_depth' => false, + 'width' => false, + 'height' => false, + 'bit_depth' => false, + 'num_channels' => false, ), ), // Standard GIF. array( DIR_TESTDATA . '/images/test-image.gif', array( - 'width' => false, - 'height' => false, - 'type' => false, - 'bit_depth' => false, + 'width' => false, + 'height' => false, + 'bit_depth' => false, + 'num_channels' => false, ), ), // Animated AVIF. From 036ab53f666b4b8fce361b8c0c3e08942c06a0e4 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Mon, 29 Jan 2024 16:33:48 -0700 Subject: [PATCH 76/79] Update src/wp-includes/class-wp-image-editor-imagick.php docblock improvement Co-authored-by: Mukesh Panchal --- src/wp-includes/class-wp-image-editor-imagick.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/class-wp-image-editor-imagick.php b/src/wp-includes/class-wp-image-editor-imagick.php index 02e3d204d7ab6..546ad3e7991bf 100644 --- a/src/wp-includes/class-wp-image-editor-imagick.php +++ b/src/wp-includes/class-wp-image-editor-imagick.php @@ -257,8 +257,10 @@ protected function update_size( $width = null, $height = null ) { $height = $size['height']; } - // If we still don't have the image size, fall back to `wp_getimagesize`. This ensures AVIF images - // are properly sized without affecting previous `getImageGeometry` behavior. + /* + * If we still don't have the image size, fall back to `wp_getimagesize`. This ensures AVIF images + * are properly sized without affecting previous `getImageGeometry` behavior. + */ if ( ( ! $width || ! $height ) && 'image/avif' === $this->mime_type ) { $size = wp_getimagesize( $this->file ); $width = $size[0]; From 93b4cdc8c635a7fa5015e0675ea2fc5e7e01fe41 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Mon, 29 Jan 2024 16:34:14 -0700 Subject: [PATCH 77/79] Update src/wp-includes/media.php Co-authored-by: Mukesh Panchal --- src/wp-includes/media.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 353810c334e62..07cc48d7197fe 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -5523,7 +5523,7 @@ function wp_getimagesize( $filename, array &$image_info = null ) { ! empty( $info ) && // Some PHP versions return 0x0 sizes from `getimagesize` for unrecognized image formats, including AVIFs. ! ( empty( $info[0] ) && empty( $info[1] ) ) - ) { + ) { return $info; } From 73c1528b8bd89fbb68af4c80cdc25f82c2a0d643 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Mon, 29 Jan 2024 16:34:57 -0700 Subject: [PATCH 78/79] Update src/wp-includes/media.php docblock cleanup Co-authored-by: Mukesh Panchal --- src/wp-includes/media.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 07cc48d7197fe..69a6d53299cab 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -5588,10 +5588,10 @@ function wp_getimagesize( $filename, array &$image_info = null ) { * @return array { * An array of AVIF image information. * - * @type int|false $width Image width on success, false on failure. - * @type int|false $height Image height on success, false on failure. - * @type int|false $bit_depth Image bit depth on success, false on failure. - * @type int|false $num_channels Image number of channels on success, false on failure. + * @type int|false $width Image width on success, false on failure. + * @type int|false $height Image height on success, false on failure. + * @type int|false $bit_depth Image bit depth on success, false on failure. + * @type int|false $num_channels Image number of channels on success, false on failure. * } */ function wp_get_avif_info( $filename ) { From c2b6df1e892ae64a0f46fa476381b720a8440658 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Mon, 29 Jan 2024 16:36:15 -0700 Subject: [PATCH 79/79] Update phpcs.xml.dist remove inadvertent exclude path for phpcs Co-authored-by: Mukesh Panchal --- phpcs.xml.dist | 1 - 1 file changed, 1 deletion(-) diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 20f3a20539e41..840bdad8fe977 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -59,7 +59,6 @@ /src/wp-includes/class-requests\.php /src/wp-includes/class-simplepie\.php /src/wp-includes/class-snoopy\.php - /src/wp-includes/class-wp-block-parser\.php /src/wp-includes/class-avif-info\.php /src/wp-includes/deprecated\.php /src/wp-includes/ms-deprecated\.php