From 42bcf794427919fe57f147540151b5c89e278123 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Thu, 16 Jul 2026 16:49:17 -0700 Subject: [PATCH 1/2] Media: Add the wp_generate_animated_image_subsizes filter to the REST index. Sub-sizes of animated images are static first-frame images by default because re-encoding every frame per sub-size is very resource intensive. The client-side media processing path can preserve animation in the browser, so expose an opt-in flag on the REST API index (alongside image_size_threshold) that the client pipeline honors for uncropped sub-sizes. See https://github.com/WordPress/gutenberg/issues/80383 and https://github.com/WordPress/gutenberg/pull/80385. --- src/wp-admin/edit-form-blocks.php | 1 + src/wp-admin/site-editor.php | 1 + .../rest-api/class-wp-rest-server.php | 21 +++++++ tests/phpunit/tests/rest-api/rest-server.php | 58 +++++++++++++++++++ tests/qunit/fixtures/wp-api-generated.js | 1 + 5 files changed, 82 insertions(+) diff --git a/src/wp-admin/edit-form-blocks.php b/src/wp-admin/edit-form-blocks.php index 44fd623fa5ad2..72963b4b741aa 100644 --- a/src/wp-admin/edit-form-blocks.php +++ b/src/wp-admin/edit-form-blocks.php @@ -89,6 +89,7 @@ static function ( $classes ) { '/?_fields=' . implode( ',', array( + 'animated_image_subsizes', 'description', 'gmt_offset', 'home', diff --git a/src/wp-admin/site-editor.php b/src/wp-admin/site-editor.php index 9a8268c3392d7..e6aca2e6fdf1a 100644 --- a/src/wp-admin/site-editor.php +++ b/src/wp-admin/site-editor.php @@ -215,6 +215,7 @@ static function ( $classes ) { '/?_fields=' . implode( ',', array( + 'animated_image_subsizes', 'description', 'gmt_offset', 'home', diff --git a/src/wp-includes/rest-api/class-wp-rest-server.php b/src/wp-includes/rest-api/class-wp-rest-server.php index af73b80103546..dc9364db769e6 100644 --- a/src/wp-includes/rest-api/class-wp-rest-server.php +++ b/src/wp-includes/rest-api/class-wp-rest-server.php @@ -1378,6 +1378,27 @@ public function get_index( $request ) { /** This filter is documented in wp-admin/includes/image.php */ $available['image_size_threshold'] = (int) apply_filters( 'big_image_size_threshold', 2560, array( 0, 0 ), '', 0 ); + + /** + * Filters whether sub-sizes of animated images should keep their animation. + * + * By default, sub-sizes of animated images (e.g. animated GIFs) are static, + * generated from the first frame only. Re-encoding every frame per sub-size + * is very resource intensive, so animated sub-sizes are opt-in. + * + * This currently only affects the client-side media processing path, + * where all frames can be decoded and re-encoded in the browser. Only + * uncropped sub-sizes keep their animation; cropped sizes (such as + * `thumbnail`) are always generated from the first frame. Uploads that + * take the server-side path also still produce static sub-sizes, as + * neither GD nor Imagick resizing preserves animation in core. + * + * @since 7.2.0 + * + * @param bool $animated_image_subsizes Whether to generate animated sub-sizes + * for animated images. Default false. + */ + $available['animated_image_subsizes'] = (bool) apply_filters( 'wp_generate_animated_image_subsizes', false ); } $response = new WP_REST_Response( $available ); diff --git a/tests/phpunit/tests/rest-api/rest-server.php b/tests/phpunit/tests/rest-api/rest-server.php index f61d2fc745d99..3246c023a67be 100644 --- a/tests/phpunit/tests/rest-api/rest-server.php +++ b/tests/phpunit/tests/rest-api/rest-server.php @@ -1207,6 +1207,64 @@ public function test_get_index() { $this->assertArrayHasKey( 'site_icon_url', $data ); } + /** + * @ticket 28474 + * + * @covers WP_REST_Server::get_index + */ + public function test_get_index_should_include_animated_image_subsizes(): void { + $user_id = self::factory()->user->create( array( 'role' => 'administrator' ) ); + $this->assertIsInt( $user_id ); + wp_set_current_user( $user_id ); + add_filter( 'wp_client_side_media_processing_enabled', '__return_true' ); + + $server = new WP_REST_Server(); + $request = new WP_REST_Request( 'GET', '/' ); + $index = $server->dispatch( $request ); + $data = $index->get_data(); + $this->assertIsArray( $data ); + + $this->assertArrayHasKey( 'animated_image_subsizes', $data ); + $this->assertFalse( $data['animated_image_subsizes'] ); + } + + /** + * @ticket 28474 + * + * @covers WP_REST_Server::get_index + */ + public function test_get_index_should_not_include_animated_image_subsizes_without_caps(): void { + add_filter( 'wp_client_side_media_processing_enabled', '__return_true' ); + + $server = new WP_REST_Server(); + $request = new WP_REST_Request( 'GET', '/' ); + $index = $server->dispatch( $request ); + $data = $index->get_data(); + $this->assertIsArray( $data ); + + $this->assertArrayNotHasKey( 'animated_image_subsizes', $data ); + } + + /** + * @ticket 28474 + * + * @covers WP_REST_Server::get_index + */ + public function test_get_index_should_honor_animated_image_subsizes_filter(): void { + $user_id = self::factory()->user->create( array( 'role' => 'administrator' ) ); + $this->assertIsInt( $user_id ); + wp_set_current_user( $user_id ); + add_filter( 'wp_client_side_media_processing_enabled', '__return_true' ); + add_filter( 'wp_generate_animated_image_subsizes', '__return_true' ); + + $server = new WP_REST_Server(); + $request = new WP_REST_Request( 'GET', '/' ); + $index = $server->dispatch( $request ); + $data = $index->get_data(); + + $this->assertTrue( $data['animated_image_subsizes'] ); + } + /** * @ticket 57902 * diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index 1be56017826ff..8e84da13246cc 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -13061,6 +13061,7 @@ mockedApiResponse.Schema = { } }, "image_size_threshold": 2560, + "animated_image_subsizes": false, "site_logo": 0, "site_icon": 0, "site_icon_url": "" From 1476c8142954e0218a70872d2c7d2266ed63bad2 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Thu, 16 Jul 2026 16:55:18 -0700 Subject: [PATCH 2/2] Reference ticket 65656 in the new REST index tests. --- tests/phpunit/tests/rest-api/rest-server.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/phpunit/tests/rest-api/rest-server.php b/tests/phpunit/tests/rest-api/rest-server.php index 3246c023a67be..081a0e8249981 100644 --- a/tests/phpunit/tests/rest-api/rest-server.php +++ b/tests/phpunit/tests/rest-api/rest-server.php @@ -1208,7 +1208,7 @@ public function test_get_index() { } /** - * @ticket 28474 + * @ticket 65656 * * @covers WP_REST_Server::get_index */ @@ -1229,7 +1229,7 @@ public function test_get_index_should_include_animated_image_subsizes(): void { } /** - * @ticket 28474 + * @ticket 65656 * * @covers WP_REST_Server::get_index */ @@ -1246,7 +1246,7 @@ public function test_get_index_should_not_include_animated_image_subsizes_withou } /** - * @ticket 28474 + * @ticket 65656 * * @covers WP_REST_Server::get_index */