Skip to content

Upload media: decode high-bit-depth AVIF via the browser - #78893

Closed
adamsilverstein wants to merge 4 commits into
trunkfrom
try/csm-hdr-avif-browser-decode
Closed

Upload media: decode high-bit-depth AVIF via the browser#78893
adamsilverstein wants to merge 4 commits into
trunkfrom
try/csm-hdr-avif-browser-decode

Conversation

@adamsilverstein

@adamsilverstein adamsilverstein commented Jun 2, 2026

Copy link
Copy Markdown
Member

What & why

Fixes the client-side-media half of #78889.

Uploading a high-bit-depth (10/12-bit) AVIF with client-side media processing active failed with a generic "File could not be uploaded". The bundled wasm-vips builds libaom with -DCONFIG_AV1_HIGHBITDEPTH=0, so it cannot decode (or encode) any AV1 stream above 8-bit. Sub-size generation threw Bitstream not supported by this decoder, every sub-size sideload failed, and the pipeline then deleted the already-uploaded original and surfaced the generic error.

Browsers decode high-bit-depth AVIF natively (the block preview already renders it). This PR uses that, reusing the exact same flow we already use for HEIC: decode the source to a JPEG once up front, then run that JPEG through the standard vips thumbnail pipeline.

How it works

  • avif-utils.tsgetAvifBitDepth() / isHighBitDepthAvif() read the bit depth straight from the ISOBMFF container (pixi / av1C boxes). No decoding; falls back to "8-bit" if it can't parse, so the common path never changes.
  • prepareItem — when an upload is a >8-bit AVIF, decode it to a JPEG once via the existing canvasConvertToJpeg() helper (the same one HEIC uses) and set that JPEG as the item's sourceFile. The original AVIF is still uploaded as the main file with generate_sub_sizes: false, so the attachment keeps its full bit depth.
  • generateThumbnails — unchanged pipeline. It generates every sub-size from sourceFile (now the JPEG) via the standard vipsResizeImage(). Sub-sizes derive their basename from the server attachment but take the source file's extension when the formats differ, so the JPEG sub-sizes of an AVIF original are named .jpg rather than .avif. Same-format uploads are unaffected.

The original AVIF is uploaded untouched, so its full bit depth is preserved. Only the generated sub-sizes are flattened to 8-bit JPEG.

There is no longer a bespoke per-sub-size canvas resize: canvasResizeImage() is removed and resizeCropItem is back to plain vipsResizeImage(). Net change is ~190 fewer lines.

Pros and cons

Pros

  • Self-contained, JS-only; no PHP and no change to the wasm-vips binary/bundle size.
  • Works on every server, including the ~70% that can't process AVIF server-side, because all processing stays on the client.
  • Reuses the existing HEIC browser-decode flow (canvasConvertToJpeg once + standard pipeline) instead of a parallel resize path.
  • The full-bit-depth AVIF original is preserved unchanged.

Cons

  • OffscreenCanvas.convertToBlob() cannot emit AVIF, so sub-sizes are written as JPEG — an AVIF attachment ends up with JPEG sub-sizes (mixed formats; WordPress handles this, but it's not ideal).
  • The canvas is 8-bit sRGB, so HDR/wide-gamut sub-sizes are flattened to 8-bit SDR (the original is unaffected).
  • Relies on the browser supporting AVIF decode (true in all current major browsers).

For an alternative that keeps sub-sizes as full-bit-depth AVIF (at the cost of a larger wasm-vips build), see the companion PR that uses a high-bit-depth libaom build.

Testing

  • Unit tests for the bit-depth parser (avif-utils) and for the new sub-size naming in generateThumbnails (AVIF original → .jpg sub-sizes), alongside the existing canvasConvertToJpeg coverage.
  • E2E: uploads a real 10-bit HDR AVIF (1920x1080_e2e_test_image_10bit_hdr.avif) and asserts the upload succeeds, the original stays image/avif at 1920×1080, and sub-sizes are generated (as JPEG). Fails on trunk (upload cancelled); passes with this change.

See #78889 for the full root-cause analysis.

The bundled wasm-vips builds libaom without high-bit-depth support
(CONFIG_AV1_HIGHBITDEPTH=0), so it cannot decode 10/12-bit AVIF. Uploading
such an image failed client-side sub-size generation, which then deleted the
already-uploaded original and surfaced a generic 'File could not be uploaded'
error.

Browsers decode high-bit-depth AVIF natively. Detect >8-bit AVIF with a cheap
container header parse (no decode) and generate its sub-sizes via the browser
decoder plus OffscreenCanvas instead of vips. The original AVIF is preserved
unchanged; sub-sizes fall back to JPEG because canvas cannot encode AVIF.

See #78889.
@adamsilverstein adamsilverstein added [Type] Bug An existing feature does not function as intended [Status] In Progress Tracking issues with work in progress [Feature] Client Side Media Media processing in the browser with WASM labels Jun 2, 2026
@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown

Size Change: +383 B (0%)

Total Size: 8.59 MB

📦 View Changed
Filename Size Change
build/scripts/upload-media/index.min.js 13.1 kB +383 B (+3.01%)

compressed-size-action

@adamsilverstein

Copy link
Copy Markdown
Member Author

Alternative approach in #78894 (high-bit-depth wasm-vips build): preserves the AVIF format and bit depth for sub-sizes, at the cost of a larger WASM binary. This PR (browser/canvas decode) is JS-only with no binary-size change but produces 8-bit JPEG sub-sizes. Both fix #78889; they're mutually exclusive.

* @param scaledSuffix Whether to add a `-scaled` suffix (for threshold resizing).
* @return Resized ImageFile (JPEG) with dimension metadata.
*/
export async function canvasResizeImage(

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.

Hmm, we shouldn't need this. Instead we can follow the HEIC approach and use the standard pipeline to process the extracted jpeg.

@github-actions

github-actions Bot commented Jun 3, 2026

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.

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>

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

The bundled wasm-vips cannot decode 10/12-bit AVIF, so generating its
sub-sizes previously required a bespoke per-sub-size canvas resize
(canvasResizeImage) routed from resizeCropItem.

Instead, decode the AVIF to a JPEG once up front in prepareItem (the same
approach already used for HEIC) and feed that JPEG to the standard vips
thumbnail pipeline as the item's sourceFile. The original AVIF is still
uploaded untouched, so the attachment keeps its full bit depth; only the
generated sub-sizes are flattened to 8-bit JPEG.

Sub-sizes derive their basename from the server attachment but take the
source file's extension when the formats differ, so JPEG sub-sizes of an
AVIF original are named .jpg rather than .avif. Same-format uploads are
unaffected.

This removes canvasResizeImage and its detection branch, a net reduction
of ~190 lines, while preserving the behavior asserted by the e2e test.
@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown

Flaky tests detected in d3c69c3.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/27035538702
📝 Reported issues:

…wser-decode

# Conflicts:
#	packages/upload-media/CHANGELOG.md
…wser-decode

# Conflicts:
#	packages/upload-media/CHANGELOG.md
#	packages/upload-media/src/store/private-actions.ts
#	test/e2e/specs/editor/various/client-side-media-processing.spec.js
@adamsilverstein

Copy link
Copy Markdown
Member Author

Superseded by #79179.

This PR worked around wasm-vips being unable to decode high-bit-depth AVIF by falling back to browser/canvas decoding. With the bump to wasm-vips@0.0.18 in #79179, wasm-vips decodes 10/12-bit AVIF natively (verified: 0.0.17 fails with error in tile, 0.0.18 decodes to a 16-bit image), so the fallback is no longer needed. Closing in favor of #79179.

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 [Status] In Progress Tracking issues with work in progress [Type] Bug An existing feature does not function as intended

1 participant