[HN Gopher] ThumbHash: A better compact image placeholder hash
___________________________________________________________________
ThumbHash: A better compact image placeholder hash
Author : minxomat
Score : 322 points
Date : 2023-03-22 19:08 UTC (3 hours ago)
(HTM) web link (evanw.github.io)
(TXT) w3m dump (evanw.github.io)
| detrites wrote:
| Anyone know why the first comparison image is rotated 90 degrees
| for both ThumbHash and BlurHash versions? Is this a limitation of
| the type of encoding or just a mistake? All other comparison
| images match source rotation.
| constexpr wrote:
| That's the only image with a non-zero EXIF orientation. Which
| probably means you're using an older browser (e.g. Chrome
| started respecting EXIF orientation in version 81+, which I
| think came out 3 years ago?). You'd have to update your browser
| for it to display correctly.
| kamikaz1k wrote:
| I don't understand why it is only for <100x100 images. Isn't the
| blurring useful for larger images? what's the point of inlining
| small ones?
| emptysea wrote:
| Probably because the algorithm is really slow and you're
| already producing a really small image so scaling your original
| image down before isn't too much work
|
| Blurhash is really slow on larger images but quick with small
| <500x500 images
| usrusr wrote:
| Thanks, even if I'm not the one who asked. I guess the longer
| version of your answer would be "if you have larger input,
| just downscale first". And for the quality demands, you
| wouldn't even miss any antialiasing, just sample every nth
| pixel for a n00xn00 image, should really be good enough.
|
| 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?
| renewiltord wrote:
| These are quite terrific. I really like these because I hate
| movement on page load. This one looks pretty good too.
| transitivebs wrote:
| I open sourced a version of what Evan calls the "webp potato
| hash" awhile back: https://github.com/transitive-bullshit/lqip-
| modern
|
| 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.
| eyelidlessness wrote:
| FWIW, it can almost certainly be moved off the main thread with
| OffscreenCanvas, but that has its own set of added
| complexities.
|
| Edit: word wires got crossed in my brain
| martin-adams wrote:
| This is nice, I really like it.
|
| 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...
| Dwedit wrote:
| Crazy idea, combine that with the technique in the submission.
| Use the chroma as-is, and average the traced luma with the
| blurry thumbnail luma.
| emptysea wrote:
| What I've seen instagram and slack do is create a really small
| jpg and inline that in the API response. They then render it in
| the page and blur it while the full size image loads.
|
| 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
| codetrotter wrote:
| Yep. I also remember a blog post from a few years ago about how
| fb removed some of the bytes in the JPEG thumbnails, because
| those bytes would always be the same in the thumbnails they
| created, so they kept those bytes separate and just added them
| back in on the client side before rendering the thumbnails
| jws wrote:
| As you get to small image sizes, the relative size of the
| quantization table and the Huffman encoding table becomes
| significant. You can easily just pick a standard version of
| these, leave them out of the image (which is no longer a
| valid JPEG), and then put them in at the destination to make
| a valid JPEG again.
|
| 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.
| degenerate wrote:
| I both love this level of optimization (for the novelty) and
| hate it (knowing all the work involved is being done with JS,
| on my machine, costing more CPU time than the bandwidth
| saved)
| explaininjs wrote:
| The claim being that the act of concatenating some
| bytestrings together is a massive time sink?
| nonethewiser wrote:
| > 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
|
| 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.
| MagicMoonlight wrote:
| That's impressively small
| gato38juega wrote:
| Curse of aros
| eis wrote:
| The results are pretty impressive. I wonder if the general idea
| can be applied with a bit more data than the roughly 21 bytes in
| this version. I know it's not a format that lends itself to be
| configurable. I'd be fine with placeholders that are say around
| 100-200 bytes. Many times that seems enough to actually let the
| brain roughly know what the image will contain.
| munro wrote:
| I hate these blurry image thumbnails, much prefer some sort of
| hole, and just wait for a better thumbnail (look at youtube for
| this, or basically any site). I'd much rather see engineers
| spending more time making the thumbnails load faster (improving
| their backend throughput, precache thumbnails, better
| compression, etc). The blurry thumbnails have 2 issues 1) trick
| person into thinking they're loaded, especially if there's a
| flicker before the blurry thumbnails are displayed!!! so then the
| brain has to double back and look at the new image. 2) have a
| meaning that content is blocked from viewing
| [deleted]
| imhoguy wrote:
| I think these issues can be solved by just rendering a spinner
| or "loading" text on top of the blurred image.
| actionfromafar wrote:
| Maybe... but solving dstraction with more distraction feels
| off somehow.
| crazygringo wrote:
| I think they're great, and it's not much different from
| progressive image loading that's been around for decades.
| Images going from blurry to sharp was a big thing back in the
| 1990's over dial-up AOL and MSN.
|
| > _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.
| layer8 wrote:
| What I find more of an issue cognitively is that they entice to
| discern their contents, but of course they are too blurry to
| really see anything and trigger the subliminal feeling that you
| forgot to put your glasses on. So they attract your attention
| while typically not providing much useful information yet. A
| non-distracting neutral placeholder is generally preferable,
| IMO. Even more preferable would be for images to load
| instantly, as many websites somehow manage to do.
| jurimasa wrote:
| This may be a super dumb question but... how is this better than
| using progressive jpegs?
| eyelidlessness wrote:
| A few things that immediately come to mind:
|
| - 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
| pshc wrote:
| Looks smoother, transparency, data small enough to inline in
| the HTML or JSON payload, supports not just JPEGs but also
| PNGs, WebPs, GIFs.
|
| IMO I don't really care for a 75%-loaded progressive JPEG. Half
| the image being pixelated and half not is just distracting.
| matsemann wrote:
| You call an API -> it returns some json with content and links
| to images -> you start doing a new request to load those images
| -> only when partially loaded (aka on request 2) you will see
| the progressive images starting to form.
|
| 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.
| derefr wrote:
| 1. If the thing that's going to be loaded isn't a JPEG, but
| rather a PNG, or WebP, or SVG, or MP4...
|
| 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.
| Scaevolus wrote:
| Nice! This would probably do even better if the color space was
| linear-- it should reduce how much the highlights (e.g. the sun)
| are lost.
| jiggawatts wrote:
| Blurring images or doing any sort of maths on the RGB values
| without first converting from the source-image gamma curve to
| "linear light" is wrong. Ideally, any such generated image should
| match the colour space of the image it is replacing. E.g.: sRGB
| should be used as the placeholder for sRGB, Display P3 for
| Display P3, etc...
|
| 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.
| btown wrote:
| Do any of the prior-art approaches, or any others, do this
| correctly?
| telios wrote:
| BlurHash kind of does, but also doesn't, as I believe the
| canvas image API respects the colorspace of the loaded image;
| you can see this by generating a blurhash from a (non-sRGB)
| image and comparing it between the browser and server
| implementations.
| eyelidlessness wrote:
| > 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.
| duskwuff wrote:
| I think you're thinking about gamma correction. Before 2009,
| Apple used a display gamma of 1.8, so images displayed
| differently than they did on Windows systems (which used a
| gamma of 2.2).
| eyelidlessness wrote:
| You're right, thanks for the correction!
| spankalee wrote:
| For these ultra-small sizes, I think I would go with Potato WebP
| since you can render it without JS, either with an <img> tag or a
| CSS background. I think it looks better too.
| Dwedit wrote:
| The potato WebP had the headers stripped off. You need JS to
| put the headers back on.
| jjcm wrote:
| FWIW, this is Evan Wallace, cofounder of Figma and creator of
| ESBuild. The dude has an incredible brain for performant web
| code.
| IvanK_net wrote:
| I think they should siply use four patches of BC1 (DXT1) texture:
| https://en.wikipedia.org/wiki/S3_Texture_Compression
|
| It allows storing a full 8x8 pixel image in 32 Bytes (4 bits per
| RGB pixel).
| attah_ wrote:
| Cool tech, but i feel that for all even remotely modern
| connection types placeholders like this are obsolete and do
| nothing but slow down showing the real thing.
| NickBusey wrote:
| And this is why everything is slow and terrible. Because us
| developers use fast machines on fast connections, and assume
| everyone else does.
|
| Travel to some far flung parts of the world, and see if your
| hypothesis holds true.
| attah_ wrote:
| Well, yes and no. There is lots of waste for sure, but this
| is really just not warranted. Honestly i'm not sure it ever
| was. Even 15-20 years ago actual pictures loaded just fine.
| And don't get me started on text placeholders..
| tshaddox wrote:
| I'm not really sure that sentiment applies here. People on
| slow or unreliable connections probably aren't going to
| rejoice that they get to see blobs of color for a while until
| the full images load, all for the cost of waiting _longer_
| for the full images and loading _more_ total data at the end
| of the ordeal.
| xwdv wrote:
| If I was in a far flung part of the world I wouldn't be
| perusing content rich bandwidth intensive websites.
|
| 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.
| abraae wrote:
| I'm in a far flung part of the world and a lot of my time
| is spent in very normal places like the AWS console, not
| doing any of those cool and dangerous sounding things.
| xwdv wrote:
| Maybe you haven't been flung far enough.
| popcalc wrote:
| When you go full lain...
| mkmk wrote:
| One pervasive source of slow connections, even in well-
| developed places, is mobile devices as they travel in a car or
| public transit.
| jbverschoor wrote:
| Until you don't have that connection somewhere.. Plus it will
| still work when your CDN / image processing server is having
| troubles.
| crazygringo wrote:
| My modern connection is a mobile network where speed very much
| comes and goes depending on where I am.
|
| There's nothing obsolete about phones on mobile networks.
| nawgz wrote:
| On the examples given, it definitely looks the best of all of
| them, and seems to be as small as or smaller than their size
|
| 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?
| tolmasky wrote:
| It appears that only BlurHash is using base83. I imagine the
| base83 encoding is being used in the table because that is what
| the library returns by default.
|
| 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.
| miohtama wrote:
| When encoded images are 20-30 bytes, few byte savings because
| of encoding seem irrelevant. But it of course depends on the
| context.
| derefr wrote:
| Unlike b64-encoding, b83-encoding is nontrivial in CPU time
| (it's not just a shift-register + LUT), so you don't want to be
| doing it at runtime; you want to pre-bake base83 text versions
| of your previews, and then store them that way, as encoded
| text. Which means that BlurHash does that on the encode side,
| but more importantly, also _expects_ that on the decode side.
| AFAIK none of the BlurHash decode implementations accept a raw
| binary; they only accept base83-encoded binary.
|
| 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.
| nosequel wrote:
| BlurHash looks not at all accurate in the examples given. Some
| are not even close. I wouldn't use it on that fact alone.
| telios wrote:
| Oddly, it looks like colorspace issues, as I've had these
| issues intermittently with BlurHash.
| a-dub wrote:
| ThumbHash? seems more like MicroJPEG maybe? hash implies some
| specific things about the inputs and outputs that are definitely
| not true!
|
| cool idea to extract one piece of the DCTs and emit a tiny low-
| res image though!
| Aeolun wrote:
| I think this is very much a one way operation, which would
| imply some form of hash?
| ed25519FUUU wrote:
| First of all, I love the idea and I think it's very creative.
|
| 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.
| Name_Chawps wrote:
| 32 kB?
| ninkendo wrote:
| I think you're 3 orders of magnitude off here, it's ~30 _bytes_
| for each image, not kilobytes.
| [deleted]
| constexpr wrote:
| Hello! I made this. People are talking about not wanting pictures
| to be initially blurry before they finish loading. I understand
| that too, and I'm not sure how I feel about it myself (I could go
| either way).
|
| 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.
| fiddlerwoaroof wrote:
| This is cool. Do you happen to know if the thumbhash string has
| other uses? Perhaps grouping images by similarity or something?
| mholt wrote:
| I just wrote a quick function to compare visual similarity
| using the thumbhash and just adding up the difference at each
| byte position seems to work really well! (As long as the
| images are the same aspect ratio. I want to do more tests.)
| bityard wrote:
| That's a whole field of study on its own, called perceptual
| hashing. I surveyed these a while for amusement and the TL;DR
| is that all immediately obvious approaches tend to have
| particularly bad corner cases.
|
| https://en.wikipedia.org/wiki/Perceptual_hashing
| efsavage wrote:
| Nice job, a material improvement over the mentioned blur hash.
|
| A nice CSS transition for when the image loaded would be the
| cherry on top ;)
| [deleted]
___________________________________________________________________
(page generated 2023-03-22 23:00 UTC)