Skip to content

Conversation

@alltheseas
Copy link
Contributor

Summary

  • Add thumbhash encoding and decoding support alongside existing blurhash
  • Thumbhash preferred over blurhash when both available (better quality, embedded aspect ratio)
  • Both hashes generated client-side during uploads for maximum compatibility with other Nostr clients
  • Both hashes included in imeta tags during transition period

Changes

  1. Dependencies: Add thumbhash crate (v0.1.0)

  2. Data structures:

    • PlaceholderHash enum with ThumbHash(Vec<u8>) and BlurHash(String) variants
    • Updated ImageMetadata and ObfuscationType to support both hash types
  3. Decoding:

    • Parse both thumbhash and blurhash from imeta tags
    • Prefer thumbhash when both present
    • Add MediaJobKind::ThumbHash for async decoding
  4. Encoding:

    • generate_thumbhash() and generate_blurhash() functions
    • Generate both hashes before NIP-96 upload
    • Include both in imeta tags when posting
  5. Tests: Unit tests for encoding/decoding roundtrip

Test plan

  • cargo test passes
  • Thumbhash decoding produces valid RGBA data
  • Blurhash decoding produces valid RGBA data
  • Both hashes generated from test image
  • Invalid images return None gracefully

Closes #1218

🤖 Generated with Claude Code

alltheseas and others added 5 commits December 15, 2025 11:18
The hyper-rustls crate requires explicit features for webpki-roots
fallback. This also refactors http_req() to improve type inference
with hyper 1.x's async body handling:

- Add webpki-roots and ring features to hyper-rustls
- Import Incoming and Response types explicitly
- Refactor redirect loop to extract content_type and body together
- Add explicit type annotations for HeaderValue closures
- Use nevernesting pattern (early return for non-redirect case)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Signed-off-by: alltheseas
Thumbhash is a more modern alternative to blurhash that provides:
- Better quality at similar hash sizes
- Embedded aspect ratio information
- Alpha channel support

Adding to workspace and notedeck/notedeck_columns crates for upcoming
encoding and decoding support.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Signed-off-by: alltheseas
Introduces thumbhash as an alternative to blurhash for image placeholders.
Thumbhash provides better quality at similar sizes and includes embedded
aspect ratio information.

Data structures:
- Add PlaceholderHash enum with ThumbHash(Vec<u8>) and BlurHash(String)
- Update ImageMetadata to use PlaceholderHash instead of blurhash string
- Add ObfuscationType::ThumbHash variant (preferred over Blurhash)
- Add MediaJobKind::ThumbHash and MediaJobResult::ThumbHash

Decoding:
- Add generate_thumbhash_texturehandle() for decoding thumbhash to texture
- Update BlurCache::get_or_request() to dispatch based on hash type
- Separate request_thumbhash_job() and request_blurhash_job() helpers

Parsing:
- Rename update_imeta_blurhashes -> update_imeta_placeholders
- Parse both "thumbhash" and "blurhash" fields from imeta tags
- Prefer thumbhash when both are available

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Signed-off-by: alltheseas
Generates both thumbhash and blurhash client-side when uploading images
for maximum compatibility with other Nostr clients. Not all clients
support thumbhash yet, so blurhash provides a fallback.

Encoding:
- generate_thumbhash(): Create base64-encoded thumbhash from image bytes
- generate_blurhash(): Create blurhash string with 4x3 components
- generate_placeholder_hashes(): Generate both hashes at once

Upload flow:
- Generate hashes before sending to NIP-96 server
- Merge with server-provided hashes (prefer server values if available)
- Include both in Nip94Event response

NIP-94 event:
- Add thumbhash field to Nip94Event struct
- Parse thumbhash from server response tags
- Include both hashes in imeta tags when posting

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Signed-off-by: alltheseas
Add comprehensive tests to verify:

blur.rs (decoding tests):
- test_thumbhash_decode: Verifies thumbhash binary decoding produces
  valid RGBA pixel data with correct dimensions
- test_blurhash_decode: Verifies blurhash string decoding produces
  valid RGBA data at specified dimensions
- test_placeholder_hash_variants: Ensures PlaceholderHash enum
  correctly stores both ThumbHash and BlurHash variants
- test_image_metadata_construction: Validates ImageMetadata struct
  construction with different hash types and optional dimensions

media_upload.rs (encoding tests):
- test_generate_thumbhash: Verifies thumbhash encoding produces
  valid base64 output that can be decoded
- test_generate_blurhash: Verifies blurhash encoding produces
  valid output that can be decoded
- test_generate_placeholder_hashes: Ensures both hashes are
  generated together for maximum compatibility
- test_invalid_image_returns_none: Confirms graceful handling of
  invalid/corrupt image data (returns None instead of panicking)

These tests ensure the encoding/decoding roundtrip works correctly
for both placeholder formats, supporting the transition period where
not all Nostr apps support thumbhash.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Signed-off-by: alltheseas
Closes: damus-io#1218
@alltheseas alltheseas force-pushed the feat/thumbhash-support branch from 467f15d to 92fbffa Compare December 15, 2025 17:47
@alltheseas
Copy link
Contributor Author

there seems to be some windows CI failure, which I don't think is related to thumbhash PR

#861 (comment)

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.

add thumbhash encoding and decoding

1 participant