But for what it's worth, I actually made this for another use case: I have a grid of images that I want to be able to zoom really far out. It'd be nice to show something better than the average color when you do this, but it would be too expensive to fetch a lot of really small images all at once. ThumbHash is my way of showing something more accurate than a solid color but without the performance cost of fetching an image. In this scenario you'd only ever see the ThumbHash. You would have to zoom back in to see the full image.
A nice CSS transition for when the image loaded would be the cherry on top ;)
Without these features, some images will have noticeable brightness or hue shifts. Shown side-by-side like in the demo page this is not easy to see, but when replaced in the same spot it will result in a sudden change. Since the whole point of this format is to replace images temporarily, then ideally this should be corrected.
As some people have said, developers often make things work for "their machine". Their machine on the "fast LAN", set to "en-US", and for their monitor and web browser combination. Most developers use SDR sRGB and are blithely unaware that all iDevices (for example) use HDR Display P3 with different RGB primaries and gamma curves.
A hilarious example of this is seeing Microsoft use Macs to design UIs for Windows which then look too light because taking the same image file across to a PC shifts the brightness curve. Oops.
(Showing my age I’m sure) I distinctly remember how frustrating this was in the bad old days before widespread browser support for PNG [with alpha channel]. IIRC, that was typically caused by differences in the default white point. I could’ve sworn at some point Apple relented on that, eliminating most of the cross platform problems of the time. But then, everything was converging on sRGB.
This shouldn't matter as long as everything is tagged with the real colorspace; it'll get converted.
If you forget to do that you can have issues like forgetting to clip floating point values to 1.0 and then you get HDR super-white when you expected 100% white.
> I'd much rather see engineers spending more time making the thumbnails load faster
Generally it's a client-side bandwidth/latency issue, not something on the server. Think particularly on mobile and congested wi-fi, or just local bandwidth saturation.
> The blurry thumbnails have 2 issues 1) trick person into thinking they're loaded
I've never found myself thinking that -- a blurry-gradient image seems to be generally understood as "loading". Which goes all the way back to the 90's.
> 2) have a meaning that content is blocked from viewing
In that case there's almost always a message on top ("you must subscribe"), or at least a "locked" icon or something.
These blurry images are designed for use in photos that accompany an article, grids of product images, etc. I don't think there's generally any confusion as to what's going on, except "the photo hasn't loaded yet", which it hasn't. I find they work great.
Progressive images suck. PNG's implementation is particularly awful, as you have to use increasing amounts of brainpower to tell whether it has finished loading or not.
But is _not_ showing blurry thumbnails during image loading any better in that regard?
- an empty area would give the false impression there isn't any image at all
- a half-loaded image would give the false impression the image is supposed to be like that / cropped
- if e.g. the image element doesn't have explicit width and height attributes, and its dimensions are derived from the image's intrinsic dimensions, there will be jarring layout shifts
> 2) have a meaning that content is blocked from viewing
For you maybe. And even when so, so what? Page context, users' familiarity with the page, and the full images eventually appearing will make sure this is at most is only a temporary and short false belief.
I generally prefer using webp to BlurHash or this version of ThumbHash because it's natively supported and decoded by browsers – as opposed to requiring custom decoding logic which will generally lock up the main thread.
Take a look here: https://github.com/ascorbic/unpic-placeholder. Recently created by a Principal Engineer of Netlify, which kind of server-side-renders BlurHash images, so that they don't tax the mainthread. Maybe the same can be done for ThumbHash (I've opened an issue in that repo to discuss it)
Edit: word wires got crossed in my brain
Placeholder image ends up being about 1KB vs the handful of bytes here but it looks pretty nice
Everything is a trade off of course, if you’re looking to keep data size to a minimum then blurhash or thumbhash are the way to go
I'm not finding a source, but I think I remember the tables being in the 1-2kB range.
Cheap MJPEG USB cameras do this. Their streaming data is like JPEG but without the tables since it would take up too much bandwidth.
Isn’t that optimizing for load speed at the expense of data size?
I mean the data size increase is probably trivial, but it’s the full image size + placeholder size and a fast load vs. full image size and a slower load.
see https://engineering.fb.com/2015/08/06/android/the-technology...
It reminds me of exploring the SVG loader using potrace to generate a silhouette outline of the image.
Here's a demo of what that's like:
https://twitter.com/Martin_Adams/status/918772434370748416?s...
cool idea to extract one piece of the DCTs and emit a tiny low-res image though!
I do not expect a hash function's output to be used to 'reverse' to an approximation of the input (which is the primary use here). That being easy is even an unacceptable property for cryptographic hash functions, which to me are hash functions in the purest form.
I would rather call this extreme lossy compression.
the first condition is satisfied, but the second is definitely not!
I'm not really sure I understand why all the others are presented in base83 though, while this uses binary/base64. Is it because EvanW is smarter than these people or did they try to access some characteristic of base83 I don't know about?
While the individual space savings per preview is small, on the backend you migh be storing literally millions/billions of such previews. And being forced to store pre-baked base83 text, has a lot of storage overhead compared to being able to storing raw binaries (e.g. Postgres BYTEAs) and then just-in-time b64-encoding them when you embed them into something.
As to why everyone else uses base64, I figure it's because base64 is what you'd have to inline in the URL since it's the only natively supported data URL encoding.
In other words, in order to take advantage of the size savings of base83, you would have to send it in a data structure that was then decoded into base64 on the page before it could be placed into an image (or perhaps the binary itself). Whereas the size savings of the base64 can be had "with no extra work" since you can inline them directly into the src of the image (with the surrounding data:base64 boilerplate, etc.) Of course, there are other contexts where the base83 gives you size savings, such as how much space it takes up in your database, etc.
Travel to some far flung parts of the world, and see if your hypothesis holds true.
I’d be smoking a cig sitting on a worn mattress in the highest floor of an abandoned apartment building typing on a late 90s ThinkPad, negotiating prices on stolen credit card lists through encrypted IRC channels and moving illicit files on SSH servers based in foreign countries.
There's nothing obsolete about phones on mobile networks.
2. These are usually delivered embedded in the HTML response, and so can be rendered all at once on first reflow. Meanwhile, if you have a webpage that has an image gallery with 100 images, even if they're all progressive JPEGs, your browser isn't going to start concurrently downloading them all at once. Only a few of them will start rendering, with the rest showing the empty placeholder box until the first N are done and enough connection slots are freed up to get to the later ones.
With this: You call an API -> it returns some json with content and links to images and a few bytes for the previews -> you immediately show these while firing off requests to get the full version.
So I'm thinking quicker to first draw of the blurry version? And works for more formats as well.
- you can preload the placeholder but still lazy load the full size image
- placeholders can be inlined as `data:` URLs to minimize requests on initial load, or to embed placeholders into JSON or even progressively loaded scripts
- besides placeholder alpha channel support, it also works for arbitrary full size image formats
IMO I don't really care for a 75%-loaded progressive JPEG. Half the image being pixelated and half not is just distracting.
It allows storing a full 8x8 pixel image in 32 Bytes (4 bits per RGB pixel).
That sounds inferior. From the article:
> ThumbHash: ThumbHash encodes a higher-resolution luminance channel, a lower-resolution color channel, and an optional alpha channel.
You want more bits in luminance. And you probably also don't want sRGB.
A software decoder would be tiny and you can use an existing good BC1 encoder.
Also, love that this comes with a reference implementation in Swift. Will definitely keep it in mind for future projects.
Blurhash is really slow on larger images but quick with small <500x500 images
I wonder if it might be nice to allocate some more bytes to the center than to the edges/corners? Or is this already done?
either way this is awesome, and thanks for sharing
As for my impression, but I don't think the blurry images is impressive enough to load an additional 32 kB per image. I think the UX will be approximately the same with a 1x1 pixel image that's just the average color used in the picture, but I can't test that out.
With some language-independent tests in such a repository, you might be able to semi-automatically convert the code into different languages, and continue with code scanning and optimizations.
Anyway: very nice work!