Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MSC2448: Using BlurHash as a Placeholder for Matrix Media #2448

Open
wants to merge 52 commits into
base: old_master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
8ba6071
Add blurhash MSC
anoadragon453 Feb 27, 2020
f71535a
some cleanup
anoadragon453 Feb 27, 2020
8adf2b2
Add blurhash to img tag, make things optional, /upload mod
anoadragon453 Feb 28, 2020
56703fc
unstable prefixes, no encoding needed
anoadragon453 Feb 28, 2020
793107d
Move m.r.m msgtype into the m.r.m section
anoadragon453 Feb 28, 2020
777c30c
hm
anoadragon453 Feb 28, 2020
b80822e
data-mx-blurhash
anoadragon453 Feb 28, 2020
b240d92
Must HTML encode, link to blurhash algo description
anoadragon453 Feb 28, 2020
1afad01
New endpoint, not new key on existing endpoint
anoadragon453 Feb 28, 2020
60773e4
No HTML encoding warning
anoadragon453 Feb 28, 2020
df5b6d7
Straight mxc->blurhash endpoint
anoadragon453 Feb 28, 2020
571ce2a
Unstable feature flag
anoadragon453 Mar 3, 2020
e885eae
blurhash in "info", download->blurhash
anoadragon453 Mar 3, 2020
4b83c51
Merge branch 'anoa/blurhash' of github.com:matrix-org/matrix-doc into…
anoadragon453 Mar 3, 2020
b893c21
Get an image from a blurhash, get a blurhash from an MXC url
anoadragon453 Mar 3, 2020
6e8eb59
Merge branch 'anoa/blurhash' of github.com:matrix-org/matrix-doc into…
anoadragon453 Mar 3, 2020
3695ecf
No GET body
anoadragon453 Mar 3, 2020
842c2a0
Update proposals/2448-blurhash-for-media.md
anoadragon453 Apr 3, 2020
3994010
Explicitly state blurhashes are not exempt from encryption
anoadragon453 Aug 18, 2020
616bc81
/upload -> xyz.amorgan.blurhash instead
anoadragon453 Aug 18, 2020
e0a7442
Add blurhashes to URL previews
anoadragon453 Jan 26, 2021
385be8a
Clarify that 'blurhash' should go in content.info
anoadragon453 Jan 26, 2021
708b756
formatting
anoadragon453 Jan 26, 2021
b761b06
Add blurhash to m.sticker events
anoadragon453 Jan 26, 2021
40d71ff
Add m.room.member
anoadragon453 Jan 26, 2021
7f13184
Add m.room.avatar
anoadragon453 Jan 26, 2021
1300a6e
Update unstable prefixes section for new event types
anoadragon453 Jan 26, 2021
2a02d2c
Specify endpoints that will need to be modified to support avatar blu…
anoadragon453 Jan 26, 2021
7ea82b4
Remove the controversial /_matrix/media/r0/blurhash endpoint
anoadragon453 Jan 26, 2021
f93d708
Format the Unstable prefixes section a little more nicely
anoadragon453 Jan 26, 2021
ed9ed5e
grammar
anoadragon453 Jan 26, 2021
48c4d55
Add a quick note about encrypted media and server-side blurhash calc
anoadragon453 Jan 26, 2021
fba60db
Rename with a more generic title
anoadragon453 Jan 26, 2021
c837c83
Riot -> Element
anoadragon453 Jan 26, 2021
b3f1915
Slight title adjustment
anoadragon453 Jan 26, 2021
5b8c191
Add note about negligible DoS potential of large blurhashes
anoadragon453 Jan 26, 2021
63d4966
Add note about invalid BlurHashes
anoadragon453 Jan 26, 2021
676571f
Move blurhash to m.room.avatar's content.info dict
anoadragon453 Apr 1, 2021
a302197
Merge branch 'master' of github.com:matrix-org/matrix-doc into anoa/b…
anoadragon453 Apr 1, 2021
594bcee
Apply suggestions from code review
anoadragon453 Apr 13, 2021
64116ae
Clarify that clients must calculate blurhashes for encrypted media
anoadragon453 Apr 13, 2021
9bd0ba6
Fix inconsistency of blurhashes MUST vs SHOULD be omitted
anoadragon453 May 22, 2021
e7e0fb7
blurhashes can be inserted into more than just m.room.message events
anoadragon453 May 22, 2021
1d954f0
Remove the need for an unstable_features entry
anoadragon453 May 22, 2021
9e981ba
Make blurhash generation on the server opt-in
anoadragon453 May 22, 2021
75a4fa6
Clarify why we place blurhash under the matrix:image:blurhash OG prop
anoadragon453 May 22, 2021
934b6d7
Mention that the generate_blurhash query param needs an unstable prefix
anoadragon453 May 22, 2021
974d368
lint proposal markdown
anoadragon453 Aug 1, 2022
5668397
Apply suggestions from TravisR
anoadragon453 Aug 1, 2022
234877c
Clear client recommendations for blurhashes vs. thumbnails
anoadragon453 Aug 1, 2022
abf5283
r0 -> v3; update links to new spec website
anoadragon453 Aug 1, 2022
754fa7a
Add and update request/repsonse examples
anoadragon453 Aug 1, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 183 additions & 0 deletions proposals/2448-blurhash-for-media.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
# MSC2448: Using BlurHash in Media Events
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved

[BlurHash](https://blurha.sh) is a compact representation of a placeholder
for an image (or the frame of a video). Currently in Matrix, clients must
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved
display a placeholder image in the message timeline while a piece of media is
loading. Some clients, such as Riot, simply display an empty space.

While thumbnails exist to combat this to some degree, they still need to be
downloaded from a homeserver, which is not instantaneous.

Instead, a BlurHash can be sent inside the `m.room.message` event, which upon
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved
receipt other clients can render for a pretty preview while the actual
thumbnail downloads. They also do not contain any `"` characters, making them
simple to stick inside existing JSON blobs.

To be clear: A BlurHash does not replace a thumbnail - it will be shown
before the thumbnail is downloaded.
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved

## Proposal

### m.room.message

A new optional field is added in `m.room.message`'s content field called
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved
`blurhash`. It is a BlurHash of the original piece of media. Clients could
then render this using [one of the available BlurHash
implementations](https://github.com/woltapp/blurhash).

This would be optionally displayed while the thumbnail of the media is loaded
in parallel.

Example `m.room.message` content field:

```
{
"body": "image.png",
"info": {
"size": 149234,
"mimetype": "image/png",
"thumbnail_info": {
"w": 301,
"h": 193,
"mimetype": "image/png",
"size": 72958
},
"w": 301,
"h": 193,
"thumbnail_url": "mxc://example.org/abcdefg",
"blurhash": "JadR*.7kCMdnj"
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved
},
"msgtype": "m.image",
"url": "mxc://example.org/abcde",
}
```

Note that a BlurHash representation is really only applicable to media, and
as such should only be used in conjunction with the following
`m.room.message` msgtypes:

* `m.image`
* `m.video`

### Inline images

An optional attribute is added to `<img>` tags in messages:
`data-mx-blurhash`, where the value of the attribute is the blurhash
representation of the inline image.

This would be optionally displayed while the inline image itself is loaded in
parallel.

Example `m.room.message.formatted_body`:

```
"formatted_body": This is awesome <img alt=\"flutterjoy\" title=\"flutterjoy\" height=\"32\" src=\"mxc://matrix.example.org/abc\" data-mx-blurhash=\"LEHV6nWB2yk8pyo\" />
```

## Calculating a blurhash

BlurHashes are inserted into `m.room.message` events by the client, however
some clients may not be able to implement the BlurHash library for whatever
reason. In this case, it would be nice to allow the media repository to
calculate the BlurHash of a piece of media for the client, similar to how
thumbnails are calculated by media repositories today.

The
[`/_matrix/media/r0/upload`](https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-media-r0-upload)
endpoint response is modified to include an optional `blurhash` key,
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved
t3chguy marked this conversation as resolved.
Show resolved Hide resolved
which the client may use to insert into messages if desired:
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved

Example response:

```
{
"content_uri": "mxc://example.com/abcde123",
"blurhash": "LKO2?U%2Tw=w]~RB"
}
```

A server MUST also be able to render a BlurHash image from a BlurHash string.
This is to support clients that do not have a BlurHash client implementation
or are otherwise unable to do so themselves.
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved

This takes the form of a new endpoint, `GET
/_matrix/media/r0/blurhash/{encodedBlurHash}`. This endpoint supports the following query parameters:

* `width` - The width in pixels of the returned BlurHash image
* `height` - The height in pixels of the returned BlurHash image

Note that implementations should be careful to limit the size of accepted
BlurHash strings, as not to overload the server with processing an obscenely
long string. If this is the case, the server should return a `400
M_TOO_LARGE`.

Example request:

```
GET /_matrix/media/r0/blurhash/LG.F5%5D%3B%2BYk%5E6%23%25*%2B%2CK-%3A9%3D%3F%40%5B
```

Example response:

```
<bytes representing BlurHash image>
```

In addition, the server can return the BlurHash string for an image when
given an MXC URL. This would be through something like the Media Information
API (specified in
[MSC2380](https://github.com/matrix-org/matrix-doc/pull/2380)), or similar.

## Visualisation

Viewing an image message that is loading:

![blurhashed preview](images/2448-blurhash.png)

Once the image loads:

![the image has loaded](images/2448-loaded-image.png)

For reference, the current state of things in Riot is:

![boo, sad](images/2448-current-state.png)

## Alternatives

We could include a base64 thumbnail of the image in the event, but blurhash
produces much more efficient textual representations.
Comment on lines +323 to +326
Copy link
Member Author

Choose a reason for hiding this comment

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

Discuss the alternative algorithm (and self-proclaimed improvement over BlurHash) ThumbHash.

Copy link
Contributor

Choose a reason for hiding this comment

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

The alpha support is nice.

Copy link
Member Author

Choose a reason for hiding this comment

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

A comparison of ThumbHash vs. BlurHash.

Pros

Alpha support

ThumbHash handles images with transparent backgrounds in a much nicer fashion than BlurHash. While the obvious use case for this is stickers, as mentioned elsewhere, I think that vector outlines would be the best way to represent stickers before they are downloaded in full resolution.

Still, being able to somewhat make out what an image with a transparent background is before it has loaded is valuable.

image

Better quality

ThumbHash appears to generally create a better representation of the image than BlurHash (examples taken from https://evanw.github.io/thumbhash/):

image

image

image

Cons

Limited Library Support

BlurHash was one of the first to widely publicise this use case, and thus it is a lot more popular than ThumbHash. Compare the number of implementations for ThumbHash versus BlurHash.

Still, the algorithm is so simple that you could presumably translate it into your chosen language in about 30m.

BlurHash is already widely used in Matrix

Again, due to BlurHash coming out much earlier than ThumbHash, Matrix clients have already implemented BlurHash (through this MSC) widely. If we switch, clients with concern for backwards-compatibility will likely need to implement both BlurHash and ThumbHash.

However, currently BlurHash mostly applies to media sent in the timeline, which quickly becomes stale. Element Web only supports m.image events in the timeline. Nheko's BlurHash support extends to both stickers and map previews on location events. I'd be most concerned about room/user avatars, which rarely change. But I've not yet seen clients implement support for that yet.

The more pressing concern would be interacting with older clients that still only send BlurHashes instead of ThumbHashes. However the failure mode here during the transition period wouldn't be too bad - you just won't see blurred thumbnails.

Bandwidth

TODO: I'd like to conduct a test of both algorithms over a range of, say, 100 images. Using base83 encoding for both. Currently I'm not sure whether BlurHash or ThumbHash generally produces smaller encoding sizes.

Copy link

Choose a reason for hiding this comment

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

ThumbHash is also around 10x faster to generate, at least on iOS. I can run a little experiment and report the results if there's interest.

The Circles client is no longer generating BlurHashes. We will continue to display them but we are moving entirely to ThumbHash for the future. The slow performance was a big part of this, but also ThumbHash just seems to work better all around. For example, we were having issues on Android where the BlurHash code crashed the app on an invalid input.

Copy link
Member

Choose a reason for hiding this comment

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

As the person who implemented Blurhash based on this MSC in Element Web I'm all for switching to something which supports alpha and has better performance, maintaining blurhash rendering similar to how @cvwright described another client handling it for some time is acceptable.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thumbhash shouldn't be significantly faster. What you are probably seeing is that most of the thumbhash libraries downscale images to 32x32 pixels before generating the hash, while blurhash libs expect the library user to do that. If you do such an experiment, make sure you take that into account.

Copy link

Choose a reason for hiding this comment

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

No, I downsample the source image once to 100x100. Then from that 100x100 I create the ThumbHash and the BlurHash.

Maybe the Swift ThumbHash implementation is just more optimized than the BlurHash version? The author went to some pretty great lengths to make it fast.

Copy link
Contributor

Choose a reason for hiding this comment

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

It doesn't look particularly optimized, but neither was the blurhash implementation, I guess. But feel free to bench it. Seems like I was also wrong, the hashing doesn't downscale first, but rendering the blurhash does create a 32x32 pixels image at best, which would be 10x faster than creating a 100x100 pixels image. So would be interesting to see comparisons, especially if you include my lib: https://github.com/Nheko-Reborn/blurhash :)

Copy link
Member Author

Choose a reason for hiding this comment

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

@cvwright If you are doing a benchmark, reporting on the resulting filesizes of the hashes would be appreciated!

Copy link
Member

Choose a reason for hiding this comment

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

@anoadragon453 if this is to progress we probably need to update the MSC to define the field for the thumbhash to live. The lack of alpha channel in blurhash makes it insufficient in my opinion.

I have a PoC impl for EW which sends & renders both blurhash & thumbhash, preferring the latter for rendering when available.

Blurhash
image
Thumbhash
image
Original
image

PoC: content["xyz.amorgan.thumbhash"] base64 encoded matrix-org/matrix-react-sdk#12164


## Backwards compatibility

Older clients would ignore the new `blurhash` parameter.

Newer clients would only show it if it exists.

## Unstable prefixes
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved

Implementations wishing to add this before this MSC is merged can do so with
the following:

The `blurhash` key in `m.room.message` should be replaced with
`xyz.amorgan.blurhash`.

`/_matrix/media/r0/upload` should be replaced with
`/_matrix/media/unstable/xyz.amorgan/upload`, which keeps the same `blurhash`
response key.
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved

`/_matrix/media/r0/blurhash/{serverName}/{mediaId}` should be
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved
replaced with
`/_matrix/media/unstable/xyz.amorgan/blurhash/{serverName}/{mediaId}`.
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved

The `data-mx-blurhash` attribute in `<img>` tags should be replaced with
`data-xyz-amorgan-blurhash`.

And finally, an entry should be added to the homeserver's `GET
/_matrix/client/versions` endpoint, in `unstable_features`, with the key
`xyz.amorgan.blurhash` set to `true`.

## Links

BlurHash's algorithm description can be found
[here](https://github.com/woltapp/blurhash/blob/master/Algorithm.md), which
also includes the full output character set.
Binary file added proposals/images/2448-blurhash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added proposals/images/2448-current-state.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added proposals/images/2448-loaded-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.