Skip to content

Blurhash placeholders for AP photo-post attachments#159

Merged
kraftbj merged 9 commits into
trunkfrom
add/blurhash-DOTCOM-17159
May 20, 2026
Merged

Blurhash placeholders for AP photo-post attachments#159
kraftbj merged 9 commits into
trunkfrom
add/blurhash-DOTCOM-17159

Conversation

@kraftbj
Copy link
Copy Markdown
Contributor

@kraftbj kraftbj commented May 19, 2026

Refs DOTCOM-17159.

Follow-up to the photo-post federation umbrella (DOTCOM-17143) — the last user-visible gap between FOSSE photo posts and native Pixelfed/Mastodon uploads.

Summary

Compute a Blurhash placeholder string at upload time and add it to outbound ActivityPub attachment[].blurhash, so Pixelfed and Mastodon paint the colored-blur preview while the full image loads. Without it, a Pixelfed grid mixing native uploads and our federated WP photos shows grey/empty cells where the native uploads paint instantly with color.

AT-side (Bluesky, Pinkleap) is unaffected — app.bsky.embed.images doesn't carry blurhash; PR 156 already emits aspectRatio for layout reservation on those clients.

What it does

A new Blurhash class hooks three things:

  1. wp_generate_attachment_metadata — for image attachments, schedule a single-event cron (fosse_blurhash_compute) to encode the hash. Upload UI returns immediately; encoding doesn't block the publish flow. Idempotent so re-uploads / metadata regens don't double-queue.
  2. fosse_blurhash_compute cron action — call the encoder, store the result as attachment postmeta (_fosse_blurhash).
  3. activitypub_attachment filter — when an image attachment has a stored hash, add blurhash to the array. No-op for non-image types, missing meta, or malformed arrays.

A second class, Blurhash_CLI, registers wp fosse blurhash backfill (with --dry-run, --limit=N, --force) for sites with media uploaded before this feature was active. Registration is gated on WP_CLI so no overhead on web requests.

Architecture notes

  • Encoder source size: thumbnail (~150px). Blurhash encoding is O(N) over pixel count and the output is just a few low-frequency DCT coefficients — feeding a 12-megapixel original burns CPU to produce a hash perceptually identical to the one computed off the thumbnail. Mastodon's own implementation samples down for the same reason.
  • GD-only: the encoder needs an [r,g,b][][] pixel array, and GD's imagecreatefrom* family is the only host-portable way to build one. Imagick has its own surface; not handled here. Sites without GD just don't get blurhash placeholders — federation is unaffected. Same posture for any other failure (unreadable file, encoder exception, deleted attachment): never blocks upload, never blocks federation.
  • Library: kornrunner/blurhash (MIT, pure PHP, ~1.8M Packagist downloads, no extension reqs of its own). The algorithm itself is well-defined by the Wolt spec — porting it inline buys very little and costs forever-maintenance of DCT code we'd otherwise never touch.
  • Per-attachment, not per-photo-post: the projector hooks activitypub_attachment (per-attachment) rather than activitypub_attachments (per-post). Non-photo posts that happen to include an image (rare today, common tomorrow) get the field too.

Files

  • src/class-blurhash.php — encoder + storage + hooks.
  • src/class-blurhash-cli.phpwp fosse blurhash backfill command.
  • tests/php/BlurhashTest.php — 28 cases covering storage helpers, encoder happy + failure paths (corrupt bytes, missing file, non-image, deleted attachment), scheduler idempotency, cron handler, AP injection happy + every defensive no-op, end-to-end round trip.
  • fosse.php — register both classes on init (same posture as the sibling projectors).
  • src/class-photo-post.php — docblock update; blurhash is no longer scoped out.
  • composer.json / composer.lockkornrunner/blurhash ^1.2.

Out of scope (separate tickets when needed)

  • Imagick fallback for hosts without GD. File-when-needed; GD is universally available on every WP host I've ever seen.
  • Composer UI surfacing the blurhash preview (Atmosphere-style).
  • Per-attachment manual override of the encoded hash.
  • Video blurhash (Mastodon supports it for video previews; WP photo posts don't federate videos today).

Test plan

  • composer test-php — full suite 704 tests / 1568 assertions pass (28 new in BlurhashTest).
  • composer lint-php — clean.
  • Manual smoke: with the branch active, upload a new image attachment, confirm the cron event fires within a minute and _fosse_blurhash postmeta gets populated.
  • Manual smoke: publish a photo post using the encoded attachment, fetch the outbound ActivityPub envelope, confirm attachment[0].blurhash is present.
  • Manual smoke: view the federated post on Pixelfed under slow-network throttle, confirm the cell paints with a colored blur instead of grey while the full image loads.
  • Manual smoke: same on Mastodon.
  • Manual smoke: run wp fosse blurhash backfill --dry-run on a site with pre-existing media, confirm it reports the expected count. Re-run without --dry-run and verify postmeta lands.

Copilot AI review requested due to automatic review settings May 19, 2026 20:07
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds Blurhash support to FOSSE so outbound ActivityPub image attachments include attachment[].blurhash, enabling Pixelfed/Mastodon to render colored-blur placeholders while images load. This fits into FOSSE’s federation “projectors” by computing/storing the hash at upload time (cron) and injecting it during ActivityPub attachment rendering.

Changes:

  • Introduces Automattic\Fosse\Blurhash to compute/store blurhashes for image attachments and inject them via activitypub_attachment.
  • Adds Automattic\Fosse\Blurhash_CLI (wp fosse blurhash backfill) for backfilling older media.
  • Adds a comprehensive PHPUnit suite covering storage, scheduling/cron behavior, encoding success/failure paths, and filter injection.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/php/BlurhashTest.php New PHPUnit coverage for blurhash storage, encoding, cron scheduling, and ActivityPub injection.
src/class-photo-post.php Updates docblock to reflect blurhash now handled by the dedicated Blurhash projector.
src/class-blurhash.php New blurhash implementation: resolve source image, encode via GD + kornrunner/blurhash, store in postmeta, inject into AP attachments.
src/class-blurhash-cli.php New WP-CLI command to backfill/force recompute blurhashes for existing attachments.
fosse.php Registers Blurhash and Blurhash_CLI on init, consistent with other projectors/bridges.
composer.json Adds kornrunner/blurhash dependency.
composer.lock Locks the newly added blurhash dependency.

Comment thread src/class-blurhash.php
Comment thread src/class-blurhash.php Outdated
Comment thread fosse.php Outdated
Comment thread src/class-blurhash-cli.php Outdated
Comment thread tests/php/BlurhashTest.php
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated 4 comments.

Comment thread src/class-blurhash.php
Comment thread tests/php/Blurhash_CLITest.php Outdated
Comment thread src/class-blurhash.php Outdated
Comment thread src/class-blurhash-cli.php
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated 4 comments.

Comment thread src/class-blurhash.php
Comment thread src/class-blurhash.php
Comment thread src/class-blurhash-cli.php
Comment thread tests/php/Blurhash_CLITest.php
@kraftbj kraftbj merged commit c1ca872 into trunk May 20, 2026
11 checks passed
@kraftbj kraftbj deleted the add/blurhash-DOTCOM-17159 branch May 20, 2026 16:57
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