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