Skip to content

Media: Generate identically-dimensioned sub-sizes only once#12506

Open
thisismyurl wants to merge 1 commit into
WordPress:trunkfrom
thisismyurl:fix/61925-subsize-dedup
Open

Media: Generate identically-dimensioned sub-sizes only once#12506
thisismyurl wants to merge 1 commit into
WordPress:trunkfrom
thisismyurl:fix/61925-subsize-dedup

Conversation

@thisismyurl

Copy link
Copy Markdown

Intermediate sub-sizes are written to disk named by their dimensions ({name}-{width}x{height}.{ext}, from WP_Image_Editor::get_suffix()). So when two registered sizes share the same width, height, and crop, they resolve to the same file. _wp_make_subsizes() currently calls make_subsize() once per size name, which re-encodes and re-writes that identical file a second time. The reporter's example is a WooCommerce install where woocommerce_gallery_thumbnail happens to equal the core thumbnail size; with many sizes registered this redundant work slows uploads and is a common cause of upload timeouts.

The fix groups the requested sizes by their resulting file (width, height, crop) before generating, produces each unique file once, and assigns the result to every size name that requested it. Because the sub-sizes were already sharing a filename, the stored sizes metadata is unchanged. The only thing removed is the duplicate image work.

A couple of notes on the approach:

  • The grouping runs before both generation paths, so the make_subsize() loop and the multi_resize() fallback both benefit. The incremental wp_update_attachment_metadata() save after each generated file is preserved, so a partial result still survives an error or timeout part-way through.
  • Grouping is by the requested width/height/crop, which is a conservative signature: two sizes are only merged when they are guaranteed to produce an identical file. Sizes that happen to resolve to the same output from different requests (for example two soft-crop sizes that both end up width-constrained) are not merged, which matches today's behavior for that case.

The regression test registers two identical sizes plus one distinct size and, using a WP_Image_Editor_GD subclass that counts make_subsize() calls, asserts two files are generated rather than three, and that both identical size names reference the same file. It fails without the grouping (three calls) and passes with it.

One disclosure on verification: this box has no Docker/wp-env, so I could not run the full PHPUnit suite locally. I proved the grouping logic with a standalone harness and wrote the committed test to the PHPUnit harness with @ticket 61925; it needs GD (it skips otherwise) and I'd appreciate a CI run confirming it.

Trac ticket: https://core.trac.wordpress.org/ticket/61925

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 4.8
Used for: Helping trace the sub-size generation path and draft the regression test. I reviewed, tested, and take responsibility for the final code.


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

Sub-sizes are stored on disk by their dimensions, so two registered sizes with
the same width, height, and crop resolve to the same file. _wp_make_subsizes()
previously re-encoded and re-wrote that file once per size name, which slows
uploads and can cause timeouts when many sizes are registered (for example a
plugin size equal to a core size).

Group sizes by their resulting file and generate each once, sharing the result
with every size name that requested it. The stored metadata is unchanged; only
the redundant image work is removed.

Adds a regression test asserting identical sizes trigger a single make_subsize()
call and share one file.

Props thisismyurl.
See #61925.
Copilot AI review requested due to automatic review settings July 13, 2026 18:41
@github-actions

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props thisismyurl.

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

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR optimizes intermediate image sub-size generation by deduplicating work when multiple registered image size names would produce the same on-disk {width}x{height} file, avoiding redundant re-encodes/writes during uploads.

Changes:

  • Group requested sub-sizes by a signature (width/height/crop) so each unique output is generated once and shared across all requesting size names in _wp_make_subsizes().
  • Apply the same deduplication to both the make_subsize() path and the multi_resize() fallback path.
  • Add a PHPUnit regression test using a GD editor spy to assert identical sizes only trigger one generation call and share metadata.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/wp-admin/includes/image.php Groups identical requested sub-sizes so only one file is generated per unique (width/height/crop), and shares the resulting metadata across size names.
tests/phpunit/tests/image/makeSubsizes.php Adds a regression test ensuring identical size requests only generate once and both names reference the same resulting meta entry.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +537 to +541
foreach ( $size_groups as $signature => $group ) {
$unique_sizes[ $signature ] = $group['data'];
}

$created_sizes = $editor->multi_resize( $unique_sizes );
Comment on lines +27 to +32
public function tear_down() {
remove_all_filters( 'wp_image_editors' );
$this->remove_added_uploads();

parent::tear_down();
}
@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants