Skip to content

Add an opt-in filter to generate animated image sub-sizes - #80385

Open
adamsilverstein wants to merge 40 commits into
trunkfrom
add/80383-animated-subsizes-optin
Open

adamsilverstein wants to merge 40 commits into
trunkfrom
add/80383-animated-subsizes-optin

Conversation

@adamsilverstein

@adamsilverstein adamsilverstein commented Jul 16, 2026 •

Copy link
Copy Markdown
Member

What?

Adds a developer opt-in filter, wp_generate_animated_image_subsizes, that re-enables animated (multi-frame) sub-size generation for animated GIFs in the client-side media processing pipeline:

add_filter( 'wp_generate_animated_image_subsizes', '__return_true' );

Fixes #80383.
Also fixes the 12-year-old core request Trac #28474 - "WordPress destroys animation in animated GIF when it resizes", as proposed in comment:58.

Core backport: Trac #65656 / WordPress/wordpress-develop#12572.

Note

Stacked on #80268, which makes static first-frame sub-sizes the default. This PR adds the opt-in path back on top of it. Only the last commit is new; review the diff from fix/animated-gif-subsize-performance.

Why?

#80268 switched sub-sizes of animated GIFs to static first-frame images, matching what WordPress core has always done server-side, because full animated re-encodes were extremely expensive (~88 s combined for a 769-frame GIF, with sub-sizes larger than the original - see #80266).

But there is long-standing, sustained demand for resized GIFs that keep their animation: Trac #28474 has been open since 2014 and stalled server-side because GD cannot do it and Imagick is only available on a subset of hosts. Client-side processing sidesteps both constraints - the cost is paid once in the uploading user's browser, and wasm-vips is available regardless of host configuration. That makes animated sub-sizes reasonable as an explicit developer opt-in while keeping the fast, core-consistent static behavior as the default.

How?

The flag follows the exact same path as image_strip_meta / image_max_bit_depth (#80218):

  1. PHP (lib/media/load.php): wp_generate_animated_image_subsizes (boolean, default false) is applied in gutenberg_media_processing_filter_rest_index() and exposed as animated_image_subsizes on the REST API root index. The field is also added to the preload/entities field lists (lib/compat/wordpress-7.1/preload.php, packages/core-data/src/entities.js), which must match exactly.
  2. Editor settings: mapped to a generateAnimatedImageSubsizes setting in use-block-editor-settings.js and forwarded by use-media-upload-settings.js.
  3. Upload pipeline: resizeCropItem reads the setting from the @wordpress/upload-media store and passes it to the vips worker as a new preserveAnimation option on resizeImage() (options object from Client Side Media: Consolidate optional positional params into options objects in vips / upload-media #80328).
  4. Vips: when preserveAnimation is set and the resize is uncropped, resizeImage() restores the pre-Client-side media: generate animated image sub-sizes from the first frame only, matching core #80268 [n=-1] load path so all frames are decoded and re-encoded. Cropped sizes (e.g. thumbnail) always flatten to the first frame, matching the pre-existing behavior - per-frame smart-cropping is out of scope.

Tuned gifsave settings

The profiling in #80266 showed ~85% of the animated resize cost is GIF re-encoding (per-frame palette quantization), and that default gifsave settings cause the output-larger-than-input bloat. When writing an animated GIF, this PR applies the settings benchmarked there:

  • effort: 2, interframe_maxerror: 8, interpalette_maxerror: 16 - measured 4-8x faster and eliminates the size bloat.

That turns the opt-in cost from ~88 s into roughly 10-20 s for a very large GIF - still too slow to be the default, but a reasonable trade for a site that has explicitly chosen animated sub-sizes.

Notes

  • The filter name says "animated image", not "GIF", deliberately: vips can also preserve frames for animated WebP, and APNG has come up on the Trac ticket. This implementation is exercised with GIFs; the door is open.
  • Core's wp_calculate_image_srcset() still never mixes the full-size GIF and its sub-sizes in one srcset. With animated sub-sizes that guard becomes overly conservative but harmless; relaxing it is a server-side follow-up.
  • Server-side uploads (Media Library taking the server path) still produce static sub-sizes, so enabling the filter reintroduces a difference between upload surfaces - this time as an explicit developer choice. Documented on the filter.
  • Guardrail interplay with Client-side media: add timeout and size guardrails to GIF to video conversion #80376 (timeout/pixel budgets, falling back to a static sub-size) is a follow-up once Media: Add timeout and size guardrails to client-side GIF to video conversion #80379 lands.

Testing Instructions

Test in WordPress Playground

  1. Enable the filter, e.g. in a mu-plugin: add_filter( 'wp_generate_animated_image_subsizes', '__return_true' );
  2. In the block editor, upload an animated GIF to an Image block and wait for the upload to finish.
  3. Inspect the attachment's medium / large sub-sizes (e.g. via /wp-json/wp/v2/media/<id>): they should be animated GIFs (multiple frames), while thumbnail (cropped) remains a static first frame.
  4. Without the filter, all sub-sizes remain static first-frame images (the Client-side media: generate animated image sub-sizes from the first frame only, matching core #80268 default), and the existing e2e test covers this.

Documentation

The client-side media docs (#75895) are updated alongside: the how-to guide gains an "Animated image sub-sizes" section for the new filter, and the architecture reference adds it to the filter table and REST index field list (plus corrects the now-stale note that sub-sizes preserve all frames).

Automated tests

  • packages/vips/src/test/resize-image.ts: new preserveAnimation suite - [n=-1] + tuned gifsave for uncropped animated resizes, first-frame flattening for crops, no effect on still formats.
  • phpunit/media/media-processing-test.php: REST index exposes generate_animated_image_subsizes (default false, honors the filter, hidden without upload_files).
  • test/e2e/specs/editor/various/gif-to-video.spec.js: new test uploads an animated GIF with the filter enabled (via a new e2e test plugin) and asserts the medium sub-size keeps all frames.
npm run test:unit packages/vips/src/test/
vendor/bin/phpunit phpunit/media/media-processing-test.php
npm run test:e2e -- test/e2e/specs/editor/various/gif-to-video.spec.js

adamsilverstein and others added 5 commits July 14, 2026 12:09
Match WordPress core's server-side behavior, where both GD and Imagick
flatten animated images when resizing and wp_calculate_image_srcset()
keeps flattened sub-sizes and the animated full-size image from mixing.

Loading all frames ([n=-1]) re-encoded a full animated GIF per uncropped
sub-size, which took 16-47 seconds per size for a 769-frame GIF and
produced sub-sizes larger than the original file (5.5MB medium from a
2.2MB source). Cropped sizes already flattened to the first frame, so
behavior was inconsistent, and Media Library uploads taking the server
path already produced static sub-sizes.

See #80266.
… output

mediabunny's default 2-second key frame cadence roughly doubles the
output size for long GIF conversions (2.2MB vs 1.14MB for a 769-frame
GIF) with no encode-time benefit. These looping, autoplaying GIF
replacements don't need fine seek granularity.

See #80266.
…size-performance

# Conflicts:
#	packages/vips/CHANGELOG.md
#	packages/vips/src/index.ts
#	test/e2e/specs/editor/various/gif-to-video.spec.js
Add the wp_generate_animated_image_subsizes filter (boolean, default
false). When a site opts in, uncropped sub-sizes of animated GIFs keep
their animation instead of flattening to the first frame, resolving the
long-standing request in https://core.trac.wordpress.org/ticket/28474
without depending on server-side Imagick availability.

The flag travels the same path as image_strip_meta: REST API root index
field -> block editor setting -> upload-media store -> vips worker,
where it restores the pre-#80268 [n=-1] load path for uncropped resizes.
When writing an animated GIF, gifsave is tuned (effort 2,
interframe_maxerror 8, interpalette_maxerror 16), measured 4-8x faster
than the defaults and avoiding sub-sizes larger than the original.

See #80383.
@adamsilverstein adamsilverstein added [Type] Enhancement A suggestion for improvement. [Feature] Client Side Media Media processing in the browser with WASM labels Jul 16, 2026
@github-actions

github-actions Bot commented Jul 16, 2026 •

Copy link
Copy Markdown

Size Change: +311 B (0%)

Total Size: 7.78 MB

📦 View Changed
Filename Size Change
build/modules/vips/worker.min.js 3.69 MB +247 B (+0.01%)
build/scripts/block-editor/index.min.js 472 kB +19 B (0%)
build/scripts/core-data/index.min.js 37.4 kB +20 B (+0.05%)
build/scripts/editor/index.min.js 549 kB +28 B (+0.01%)
build/scripts/upload-media/index.min.js 16.3 kB -3 B (-0.02%)

compressed-size-action

@adamsilverstein

Copy link
Copy Markdown
Member Author

Core backport of the server-side changes: WordPress/wordpress-develop#12572 (draft; Trac ticket to follow).

@adamsilverstein adamsilverstein added the Backport to WP 7.1 Beta/RC Pull request that needs to be backported to the WordPress major release that's currently in beta label Jul 17, 2026
@andrewserong

andrewserong commented Jul 17, 2026 •

Copy link
Copy Markdown
Contributor

Thanks for looking into this.

But there is long-standing, sustained demand for resized GIFs that keep their animation: Trac #28474 has been open since 2014 and stalled server-side because GD cannot do it and Imagick is only available on a subset of hosts.

Before reviewing, we're now past Beta 1 and this sounds like a new feature to me. Should we be pursuing this, or focusing on bug fixing / polishing tasks?

I.e. #80268 definitely sounds good to fix for the release, but I'm wondering where we should draw the line with the available time we have left for the release (and since we're both also trying to polish other features, too). Just feeling mindful of the time and attention we have to polish these things, but don't want to be a blocker needlessly of course!

@github-actions github-actions Bot added the [Package] E2E Tests /packages/e2e-tests label Sep 15, 2026
@github-actions

github-actions Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

🤖 PR meta 🤖

🎉 Props

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: adamsilverstein <adamsilverstein@git.wordpress.org>
Co-authored-by: swissspidy <swissspidy@git.wordpress.org>
Co-authored-by: t-hamano <wildworks@git.wordpress.org>
Co-authored-by: andrewserong <andrewserong@git.wordpress.org>
Co-authored-by: annezazu <annezazu@git.wordpress.org>
Co-authored-by: aduth <aduth@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Updated as activity occurs, without notifying anyone named here. Add the props-bot label to refresh.

📦 Bundle size

Size Change: +645 B (+0.01%)

Total Size: 8.29 MB

📦 View Changed
Filename Size Change
build/modules/vips/worker.min.js 3.69 MB +567 B (+0.02%)
build/scripts/block-editor/index.min.js 517 kB +18 B (0%)
build/scripts/core-data/index.min.js 79 kB +20 B (+0.03%)
build/scripts/editor/index.min.js 619 kB +37 B (+0.01%)
build/scripts/upload-media/index.min.js 16.8 kB +3 B (+0.02%)

1a1654e Run

⚡ Performance

Show the results

Client side metrics exclude the server response time.

front-end-block-theme

Metric 73ff8ee trunk % Change
timeToFirstByte 55.9 ms +7.07% -2.15% 57.65 ms +9.28% -4.94% -3.04%
largestContentfulPaint 98 ms +4.08% -8.16% 98 ms +4.08% -6.12% 0%
lcpMinusTtfb 39.05 ms +14.98% -12.42% 36.2 ms +20.72% -5.8% 7.87%
wpBeforeTemplate 27.24 ms +8.11% -0.81% 28.01 ms +15.24% -2.75% -2.75%
wpTemplate 24.02 ms +5.12% -1.92% 24.35 ms +6.45% -3.94% -1.36%
wpTotal 51.9 ms +6.88% -2.25% 53.55 ms +9.6% -5.17% -3.08%
wpMemoryUsage 7.65 MB +0% -0% 7.59 MB +0% -0% 0.82%
wpDbQueries 17 +0% -0% 17 +0% -0% 0%

front-end-classic-theme

Metric 73ff8ee trunk % Change
timeToFirstByte 46.8 ms +6.2% -2.78% 45.5 ms +9.23% -1.32% 2.86%
largestContentfulPaint 104 ms +3.85% -3.85% 100 ms +4% -0% 4%
lcpMinusTtfb 56.2 ms +3.65% -2.67% 54.8 ms +1.64% -0.55% 2.55%
wpBeforeTemplate 24.52 ms +11.91% -1.39% 24.01 ms +14.33% -1.17% 2.12%
wpTemplate 18.22 ms +3.73% -2.47% 18.06 ms +1.5% -1% 0.89%
wpTotal 43.22 ms +6.94% -2.52% 42.21 ms +8.88% -1.37% 2.39%
wpMemoryUsage 6.28 MB +0% -0% 6.20 MB +0% -0% 1.16%
wpDbQueries 14 +0% -0% 14 +0% -0% 0%

media-processing

Metric 73ff8ee trunk % Change
mediaProcessingJpeg 411.37 ms +0.95% -0.27% 418.16 ms +0.62% -0.32% -1.62%
mediaProcessingAvif 6152.59 ms +0.04% -0.15% 6157.44 ms +0.42% -0.1% -0.08%
mediaProcessingJpegToAvif 4302.69 ms +0.19% -0.12% 4313.93 ms +0.37% -0.24% -0.26%

media-upload

Metric 73ff8ee trunk % Change
jpegUploadProcessing 1450.82 ms +34.3% -1.64% 1431.78 ms +1.7% -0.57% 1.33%
pngUploadProcessing 214.2 ms +4.87% -18.68% 210.98 ms +3.53% -5.4% 1.53%
largeJpegUploadProcessing 1418.87 ms +2.12% -0.6% 1444.25 ms +0.27% -0.71% -1.76%
multipleImageUploadProcessing 1602.36 ms +31.47% -1.32% 1647.64 ms +28.98% -2.54% -2.75%

post-editor

Metric 73ff8ee trunk % Change
serverResponse 529.65 ms +2.7% -8.93% 489.99 ms +5.16% -3.15% 8.09%
firstPaint 232.72 ms +39.79% -15.25% 224.66 ms +9.67% -6.96% 3.59%
domContentLoaded 1148.65 ms +0.84% -0.48% 1125.86 ms +0.11% -2.03% 2.02%
loaded 1150.05 ms +0.85% -0.47% 1127.05 ms +0.13% -2.04% 2.04%
firstContentfulPaint 486.45 ms +1.78% -6.03% 465.59 ms +1.46% -3.3% 4.48%
firstBlock 3461.76 ms +1.38% -0.53% 3306.8 ms +0.82% -0.16% 4.69%
type 20.21 ms +7.57% -4.11% 19.92 ms +5.42% -8.89% 1.46%
typeWithoutInspector 20.63 ms +6.5% -7.71% 18.44 ms +5.26% -3.63% 11.88%
typeWithTopToolbar 28.29 ms +2.4% -2.69% 26.49 ms +6.53% -4.04% 6.8%
typeContainer 9.85 ms +2.03% -0.61% 8.85 ms +4.86% -1.36% 11.3%
focus 124.75 ms +2.47% -5.9% 79.01 ms +6.66% -11.34% 57.89%
firstFocus 254.05 ms +0% -0% 203.17 ms +0% -0% 25.04%
selectAll 607 ms +2.27% -2.01% 573.81 ms +2.36% -6.5% 5.78%
listViewOpen 84.05 ms +5.15% -8.58% 62.49 ms +12.91% -11.6% 34.5%
inserterOpen 42.36 ms +7.72% -5.1% 23.75 ms +3.49% -6.06% 78.36%
inserterHover 13.44 ms +6.85% -9.82% 2.29 ms +13.1% -10.92% 486.9%
inserterSearch 8.36 ms +5.14% -4.67% 8.06 ms +6.58% -4.09% 3.72%
loadPatterns 655.41 ms +10.73% -4.19% 631.81 ms +4.46% -4.56% 3.74%
wpTotal 519.56 ms +2.74% -9.13% 479.74 ms +5.29% -3.22% 8.3%
wpMemoryUsage 13.21 MB +0% -0% 13.13 MB +0% -0% 0.56%
wpDbQueries 54 +0% -1.85% 54 +0% -1.85% 0%

site-editor

Metric 73ff8ee trunk % Change
serverResponse 491.19 ms +3.4% -8.32% 504.03 ms +3.64% -4.57% -2.55%
firstPaint 261.83 ms +8.67% -11.15% 295.77 ms +5.33% -10.63% -11.48%
domContentLoaded 1170.86 ms +3.27% -0.44% 1190.22 ms +2.66% -1.07% -1.63%
loaded 1172.29 ms +3.25% -0.44% 1191.47 ms +2.64% -1.01% -1.61%
firstContentfulPaint 467.73 ms +5.26% -1.2% 473.08 ms +5.31% -3.46% -1.13%
firstBlock 4406.29 ms +2.43% -2.02% 4315.73 ms +0.77% -0.3% 2.1%
type 18.71 ms +6.25% -4.86% 16.62 ms +6.62% -2.83% 12.58%
navigate 127.84 ms +37.08% -5.77% 106.47 ms +16.57% -2.91% 20.07%
loadPatterns 1309.27 ms +26.23% -1.38% 1301.26 ms +2.83% -5.12% 0.62%
loadPages 1020.19 ms +1.2% -1.53% 1039.09 ms +3.41% -0.85% -1.82%
wpTotal 481.02 ms +3.5% -8.5% 494.31 ms +3.59% -4.73% -2.69%
wpMemoryUsage 12.25 MB +0% -0% 12.16 MB +0% -0% 0.73%
wpDbQueries 43 +2.33% -0% 43 +2.33% -0% 0%

1a1654e Run

adamsilverstein and others added 4 commits September 15, 2026 10:12
…mment

The `wp_generate_animated_image_subsizes` filter was tagged `@since 23.9.0`,
a version already released; 24.0.0 is at RC, so the filter lands in 24.1.0.

The 7.1 preload still described its field list as complete and required to
match entities.js exactly. It no longer is: `generate_animated_image_subsizes`
is spliced in by the 7.2 filter that runs after it. Say where the rest of the
list comes from so the next field is added in the right place.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xhje3QeZwbKAvNQQcDRnjW
A site only reaches this path by enabling `wp_generate_animated_image_subsizes`,
so quietly producing a static sub-size looks like the filter had no effect.
Warn with the frame count, frame size, and the budget the estimate was
measured against, so the degradation is visible and the constant is tunable
against real uploads.

Correct the two constants' docblocks while here. `BYTES_PER_PIXEL` claimed to
cover vips' working buffers, but four bytes is exactly RGBA with no headroom;
all of the margin comes from the budget sitting well under the heap size.
`ANIMATION_MEMORY_BUDGET` compares against the fully materialized frame stack,
which is an upper bound rather than an estimate: libvips streams the
load/resize/save pipeline, so the check deliberately errs toward flattening.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xhje3QeZwbKAvNQQcDRnjW
… GIFs

Raising `interpalette_maxerror` to 16 makes frames reuse the previous
frame's palette far more often than libvips' default of 3. That only pays
off when successive palettes are already close. When the colours genuinely
shift, the reused palette no longer fits and the frame has to be dithered
harder to compensate, costing both time and accuracy.

Measured on a 48-frame colour-cycling GIF resized to 300px, 16 was ~50%
slower than the default and more than doubled the mean per-pixel colour
error, to save 1.7% of the file size. The win the tuning was added for comes
from `effort: 2` (~7x faster on its own) and, marginally, from
`interframe_maxerror: 8`; both are kept.

Assert the option is absent so the value is not reintroduced as a size tweak
without re-measuring the fidelity cost.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xhje3QeZwbKAvNQQcDRnjW

@t-hamano t-hamano left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good from my end.

  • Entries in the changelog file should link to the PR and describe the actual changes this PR. introduces to the package.
  • It is probably worth publishing a dev note.

@t-hamano t-hamano added the Needs Dev Note Requires a developer note for a major WordPress release cycle label Sep 17, 2026
adamsilverstein and others added 2 commits September 17, 2026 22:29
…changelogs

The entries linked to the issue rather than this pull request, which is
what the rest of the changelogs reference, and described the feature
rather than the interface each package gains. Name the new
`generateAnimatedImageSubsizes` setting and `preserveAnimation` option,
and state the cropped and memory-budget cases that flatten anyway.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsWVvdyjpHJYwAp2xiGJGw
@adamsilverstein

Copy link
Copy Markdown
Member Author

Got it, thanks @t-hamano - dev note makes sense, will do.

@aduth

aduth commented Sep 18, 2026

Copy link
Copy Markdown
Member

Hey 👋 I wanted to give you a heads-up since this pull request is affected by recent validation changes for changelog files.

#83043 adds additional validation for changelog files. You'll note that this pull request is currently failing a "Required changes from trunk" check.

What you'll need to do: You will need to either rebase or merge the latest code from trunk. In addition, a cursory review of open pull requests identified this pull request as potentially failing under the new validation checks. You will want to double-check that any changes to CHANGELOG.md files follow the Maintaining Changelogs guidance, which has been improved as part of these recent changes.

adamsilverstein and others added 2 commits September 18, 2026 08:23
Both entries sat under published version headings, so the changelog
structure validator failed: an entry for an unmerged PR must appear
under `## Unreleased`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bk4AjYTiZ35zVfzYXurfx5
@adamsilverstein

Copy link
Copy Markdown
Member Author

I did some manual testing on this and it worked as expected. With the filter returning true, I uploaded a sample gif and was able to see the medium and large subsizes were also animated gifs, while the cropped thumbnail variation was not animated (as expected).

I posted a simple test plugin that lets you toggle the setting as a gist: https://gist.github.com/adamsilverstein/16c35549d26024dd7266630fbd801442

…ubsizes-optin

# Conflicts:
#	packages/upload-media/CHANGELOG.md
#	packages/vips/CHANGELOG.md
Trunk's Vitest setup now fails any test that lets console.warn through
without an explicit expectation, so the memory-budget fallback test
asserts the warning through `toHaveWarnedWith` instead of a manual spy.
Gutenberg 24.1.0 shipped while this branch was open, so the filter's
@SInCE moves to 24.2.0.

Claude-Session: https://claude.ai/code/session_018uRsBsDb9t3piAjyWaycCo
@adamsilverstein

Copy link
Copy Markdown
Member Author

Thanks @t-hamano. The changelog entries in upload-media and vips now link this PR and describe the package change (the generateAnimatedImageSubsizes setting and the preserveAnimation option on resizeImage).

For the dev note, I put a proposed draft up as a gist so it can be reviewed before it goes in the field guide: https://gist.github.com/adamsilverstein/409f5cba3e689b7977c7eb31042cc5a5

The draft came out of a session with Claude Code, the gist of it:

New opt-in filter wp_generate_animated_image_subsizes, default false. With it on and client-side media processing enabled, uncropped sub-sizes of animated GIFs keep their animation; cropped sizes such as thumbnail still use the first frame. Server-side uploads are unchanged, srcset still skips animated GIFs, and a memory guard flattens a size rather than failing the upload when the frames would not fit the WASM heap.

Does that cover what you had in mind?

@t-hamano t-hamano left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adamsilverstein, thanks for the update!

I'm not very familiar with this feature, but it looks good to me overall. @swissspidy @andrewserong, could you take another look if you have the bandwidth?

// @see lib/compat/wordpress-7.2/preload.php
_fields: [
'description',
'generate_animated_image_subsizes',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this field also be added to the templates generated by wp-build?

'/?_fields=description,gmt_offset,home,image_max_bit_depth,image_sizes,image_size_threshold,image_strip_meta,name,site_icon,site_icon_url,site_logo,timezone_string,url,page_for_posts,page_on_front,show_on_front',

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, added to both page templates (plus a wp-build changelog entry) in 1a1654e.

Comment thread packages/vips/src/index.ts Outdated

@andrewserong andrewserong left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is testing great for me! Nothing to note other than what @t-hamano has already mentioned. I jotted down a few comments while I was reviewing, but they're all nits and non-blocking 🙂

In the happy path, with common GIFs the preservation of animation succeeds, and only the cropped thumbnail is a still image:

2026-10-06.12.19.09.mp4

With enormous GIFs (like those I record using Gifox for screen recordings) the conversion is correctly bailed and logged out to the console:

Image

And, of course, without the filter active, the behaviour as on trunk is preserved.

It's a neat feature — one idea for beyond 7.2 could be, if we feel like it's been working nice and stable for a release, we could always switch the default to it being switched on and/or add a block editor UI setting to allow users to toggle the feature without needing to use a filter.

For now, though, I like that this is shipped behind the filter as it feels like a safe way to roll it out and do any tweaks that might be needed as folks start to use it.

LGTM! 🚀

Comment thread lib/compat/wordpress-7.2/preload.php Outdated
Comment on lines +8 to +20
/**
* Adds the `generate_animated_image_subsizes` field to the preloaded root REST index.
*
* The preloaded `_fields` list has to match the one requested by
* `packages/core-data/src/entities.js` exactly, same fields in the same order, or the
* preloaded response is discarded and the editor requests the index again.
*
* @since 7.2.0
*
* @param array $paths REST API paths to preload.
* @return array Filtered preload paths.
*/
function gutenberg_block_editor_preload_paths_7_2( $paths ) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be simpler to just remove the wordpress-7.1/preload.php file, copy and paste its contents to this file and make the updates required to add in the generate_animated_image_subsizes value?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That approach makes sense to me 👍

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 1a72935 - the 7.1 file is gone and the 7.2 filter now owns the complete field list.

Comment on lines +29 to +31
* `height` is the full "toilet roll" height of an image loaded with
* `[n=-1]`, so `height / pageHeight` is the frame count, which drives
* the animation memory estimate.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOL at "toilet roll" height. Though I suppose it's an apt metaphor for it!

Comment thread packages/vips/src/types.ts Outdated
Comment on lines +195 to +202
/**
* Maximum inter-palette error for palette reuse.
*
* Frames whose palette is within this distance of the previous frame's
* reuse it, avoiding a costly palette recomputation per frame.
* Only used by gifsave; do not provide for any other type!
*/
interpalette_maxerror?: number;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the code comments it seems we're just using the default value for now. Is it still worth including this type for the future, or is it redundant for now?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed, removed in 02c2163.

Comment on lines +609 to +610
// Not an exact match against the source: the sub-size is written
// by cgifsave with inter-frame and inter-palette error tolerances,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Such a tiny nit (feel free to ignore!) but it seems we're using the default for inter-palette, right?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, comment fixed in 02c2163.

Comment on lines +141 to +150
| Filter | Where the value is used |
| ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `image_editor_output_format` | Builds the per-MIME output format map applied by the client. |
| `wp_editor_set_quality` / `jpeg_quality` | Resolved per registered size into the upload response's `image_quality` field. |
| `big_image_size_threshold` | Shipped to the client via the REST index; scaling happens in the browser. |
| `image_save_progressive` | Read at upload time; the client applies progressive/interlaced encoding accordingly. |
| `image_strip_meta` | Exported on the REST index; when it returns `false`, the client keeps all metadata on generated images. |
| `image_max_bit_depth` | Exported on the REST index; the client caps the output bit depth of generated images accordingly. |
| `wp_generate_animated_image_subsizes` | Exported on the REST index; when it returns `true`, uncropped sub-sizes of animated images keep their animation instead of flattening to the first frame. |
| `intermediate_image_sizes` / `intermediate_image_sizes_advanced` | Consumed when computing registered sizes and `missing_image_sizes`. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nitpicky comment here for the two .md file changes, feel free to ignore!

This PR changes the whitespace for the tables in these markdown files so that everything lines up visually in a text editor. However, I find the readability in the diff a little awkward as it means we need to update the whitespace any time we make changes to the tables.

Is it worth preserving what we had in trunk instead, which is that each column is just the size of its text? (I haven't checked what we're doing elsewhere, just noticed that this PR's diffs for these files seems bigger than it needs to be).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes good point, the spacing shouldn't be needed.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restored trunk's table formatting in 5553886.

adamsilverstein and others added 6 commits October 6, 2026 14:38
Co-authored-by: Aki Hamano <54422211+t-hamano@users.noreply.github.com>
The fallback for an animation whose later frame cannot be decoded was
silent, so a site that opted in would see a static sub-size with no
hint why. Log it like the memory-budget fallback, and cover both the
retry and the rethrow when animation is not being preserved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MqJy7qoG31Ms7amXYtNN5K
The tuned gifsave settings leave inter-palette error at the libvips
default, so the type had no caller. Also correct the e2e comment that
still described an inter-palette tolerance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MqJy7qoG31Ms7amXYtNN5K
Padding every table column to align in a text editor turned a few
added rows into a rewrite of each table. Keep trunk's compact cells so
the diff shows only the new content.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MqJy7qoG31Ms7amXYtNN5K
Splicing the new field into the 7.1 list from a second filter needed
ordering and anchor logic to keep the list matching entities.js. One
filter that owns the complete list is simpler and keeps a single place
to update.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MqJy7qoG31Ms7amXYtNN5K
core-data now requests generate_animated_image_subsizes on the root
index, so the page templates' preload path has to list it too or the
preload is never consumed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MqJy7qoG31Ms7amXYtNN5K
@github-actions github-actions Bot added the [Package] wp-build /packages/wp-build label Oct 6, 2026

@andrewserong andrewserong left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the updates, just gave it another quick smoke test again, and still testing well for me with and without the filter! :shipit:

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Feature] Client Side Media Media processing in the browser with WASM Needs Dev Note Requires a developer note for a major WordPress release cycle [Package] Block editor /packages/block-editor [Package] Core data /packages/core-data [Package] E2E Tests /packages/e2e-tests [Package] Editor /packages/editor [Package] wp-build /packages/wp-build [Status] In Progress Tracking issues with work in progress [Type] Enhancement A suggestion for improvement.

Projects

Status: 🔎 Needs Review

Development

Successfully merging this pull request may close these issues.

Client-side media processing: add an opt-in filter to generate animated GIF sub-sizes

6 participants