[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)