12 ms·
BlurHash: Algorithm to generate compact representation of an image placeholder
- css 7y agoJudging by the example string on the website, `LEHV6nWB2yk8pyoJadR*.7kCMdnj`[0], you cannot use these hash strings as filenames because they can have disallowed characters. You could save a step by just sending which image to download and use the filename as the hash you use to render the result, but this algorithm requires you store the relationship between the hash and the image somewhere (unless I am missing something obvious). [0] source code: https://github.com/woltapp/blurhash-python/blob/7469c813ea646ac022ca2879de76cc6e453237a1/src/encode.c#L125 https://github.com/woltapp/blurhash-python/blob/7469c813ea64...
- bausano_michael 7y agoI quite like that idea. Maybe you could run base64/base32 on the blurhash. That brings two potential issues I could think of. Is the size of the result less than filesystem max file name length? And what about collisions where two slightly different images have the same blurhash?
- lvh 7y agoAs I mentioned upthread: none of those are disallowed on common server platforms, but ironically canonical Base64 _will actually_ break, and it'll break rarely enough that you're likely to miss it in testing! (The last character in canonical Base64, for symbol 63b10, is /, which is disallowed on (*nix) filesystems and does not occur in the native BlurHash dictionary. Unless you're suggesting encoding the encoded format, I guess.)
- bootloop 7y agoIf you want to do that just use a different encoding which outputs a string safe to be used for filenames.
- DagAgren 7y agoThis one is picked very carefully to be as safe as reasonably possible with this number of characters. As was pointed out elsewhere, it is actually safe for filenames.
- deleted 7y ago[deleted]
- lvh 7y ago1. Using the hashes as principal indices seems unsafe in general (which includes image upload) anyway: it looks pretty simple to generate collisions. The suggested use case (save the hash in the database next to the original image or a reference to it) sounds fine. 2. Which disallowed characters? The dictionary appears to be "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~". No slashes or null bytes, which are the common disallowed characters on server platforms. It does have characters your shell cares about, but that's only a problem if you're not quoting.
- css 7y ago`:` is disallowed on MacOS and several of those characters are disallowed on Windows. Generally I view images on a frontend, not on my servers.
- lvh 7y agoFair! I would not expect the image be saved to a Windows path when displaying a blurred image in the example use cases (mobile apps and web pages). You're thinking Electron, perhaps? Point of order though: I don't think that's right for MacOS, though you're certainly right for Windows. HFS+ lets you use any Unicode symbol including NUL (because the filenames are encoded Pascal-style). I don't know the details how how APFS does it, but it also appears to support any Unicode code point, though it additionally mandates UTF-8. [0] [0] https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/APFS_Guide/FAQ/FAQ.html https://developer.apple.com/library/archive/documentation/Fi... grep "filenames"
- DagAgren 7y agoThis is a tempting idea, but as was already pointed out, the problem is not the character set, but that similar images will encode to the same string. Anyway, chances are you are already storing a filename in your database, so you would need a second field for the blurhash string.
- mzs 7y agoassets/example.png/LEHV6nWB2yk8pyoJadR*.7kCMdnj assets/example.jpg/LEHV6nWB2yk8pyoJadR*.7kCMdnj
- presumably 7y agos/g\//g#/ assets/example.png#LEHV6nWB2yk8pyoJadR*.7kCMdnj assets/example.jpg#LEHV6nWB2yk8pyoJadR*.7kCMdnj No need to send the extra data to the server for each request.
- tyingq 7y agoThey mention it's a custom version of base83: https://github.com/woltapp/blurhash/blob/master/Algorithm.md#base-83 https://github.com/woltapp/blurhash/blob/master/Algorithm.md... With this charset: 0123456789ABCDEFGHIJKLMNOPQ RSTUVWXYZabcdefghijklmnopqr stuvwxyz#$%*+,-.:;=@[]^_{|}~. I imagine you could tweak that to replace problematic chars with safe substitutes.
- DagAgren 7y agoThese are the safest I could find. They are perfectly fine for using in filenames.
- PabloSichert 7y agoHm, that's an interesting choice. I also wonder why they didn't use an encoding that has an alphabet with 2^n characters (that way you can directly map 1 charater to n bits when decoding). Coincidentally, I've just finished some work on a project[1] that is in the same space (identifiers for images). For the reasons you pointed out, I found Douglas Crockford's base32[2] encoding to be a good fit. [1] https://github.com/pablosichert/ciid https://github.com/pablosichert/ciid [2] https://www.crockford.com/base32.html https://www.crockford.com/base32.html
- DagAgren 7y agoThat choice is explained in the README: https://github.com/woltapp/blurhash https://github.com/woltapp/blurhash (83 is about as many safe characters as you can reasonably find, and it allows some nice ways of packing values together.)
- PabloSichert 7y agoCould you explain what "AC components" refers to? I couldn't figure that out just by reading your README. I wonder how the efficiency compares to just encoding on the bit level.
- DagAgren 7y agoIt is a term often used for DCT-transformed data. DCT, in this case, breaks the image down into basically an average colour of the whole image, referred to as the DC component, and a bunch of waves that make up the detail of the image, referred to as AC components. https://github.com/woltapp/blurhash/blob/master/Algorithm.md https://github.com/woltapp/blurhash/blob/master/Algorithm.md
- sampo 7y ago> Could you explain what "AC components" refers to? All but the first component of the Fourier transform. (The first component is the average of the data.) The term comes from electrical engineering, but Fourier transform has lots of applications also outside of electrical engineering.
- mlyle 7y ago> you cannot use these hash strings as filenames because they can have disallowed characters. You can't really use them as filenames because they won't be collision free, too. They're not meant to be an identifier, but instead a compact representation of the image that can be stored in a database.
- ReverseCold 7y agoI accidentally made a folder (in my pinephone's / folder) the other day called '', and I was about to delete it with rm -rf . Maybe that shouldn't be allowed, but it is :P
- tantalor 7y ago> Your client then takes the string, and decodes it into an image that it shows while the real image is loading over the network. That's not a "hash", that's an encoding. Hashes are one-way.
- bootloop 7y agoCan you show me then how you decode this string to the original image?
- lvh 7y agoThis /is/ one-way by virtue of throwing information away: you can't rebuild the original image. If you're thinking of preimage resistance (it must be hard to find an input that hashes to a given output) that's a property of cryptographic hashes, but not hashes in general (canonical example FNV).
- dahart 7y agoA hash is any function that produces a fixed size output. It’s not required to be one-way, or to even change the input. You might be thinking of a “good cryptographic” hash. For that, it’s desirable to be one-way and to scramble the input, but that’s a specific subset of hash functions. Edit: to the downvoter, please have a look through https://en.wikipedia.org/wiki/Hash_function https://en.wikipedia.org/wiki/Hash_function
- smlacy 7y agoForgot to mention one important detail: How big is the JavaScript source to the BlurHash decoding algorithm? I'm guessing that it's approximately the size of a single image, possibly quite a bit more.
- hombre_fatal 7y ago22kb Edit: Nevermind, derp, https://blurha.sh/blurhash.01ae00eea611ada5e9c7.js https://blurha.sh/blurhash.01ae00eea611ada5e9c7.js contains all of the JS of the entire website.
- lvh 7y agoIronically jpegoptim is pretty happy to knock img1.jpg (woman eating donut) down from its original 55kB to 33kB with no perceptible-to-me quality loss though ;)
- DagAgren 7y agoIt is a lot smaller than that, that is probably the size of all JS code on the web page? The algorithm itself fits into a few k.
- hombre_fatal 7y agoYeah, you're right. I found the first JS file in my network tab and judged by its blurhash.js filename that it was the algorithm: https://blurha.sh/blurhash.01ae00eea611ada5e9c7.js https://blurha.sh/blurhash.01ae00eea611ada5e9c7.js Of course, my first clue should have been that it was also the only Javascript file.
- lvh 7y agoThis is a great point! I wonder if it could be made much smaller by instead "vectorizing" the blur. Right now it appears to draw individual pixels, but perhaps it could draw a few large blurred gradiented circles instead? (I also appreciate that this is probably me getting overexcited about this, since odds are the JS is cached, and in the case of mobile apps it doesn't matter much.)
- asah 7y agoHow did this compare with progressive JPEG ?
- lvh 7y agoI think the biggest difference is in visual effect. A progressive JPEG looks pretty terrible at first, whereas a blurred image may look preferable even though it technically has less information.
- ronyfadel 7y agoAgreed, +1 for browser makers to make progressive JPEG loading not look like a dog just barfed the original image
- lvh 7y agoIt'd be neat if there was an easy way to combine progressive JPEG loading with aggressive CSS blur filters, maybe. I think onload fires when the progressive JPEG is done, so perhaps you could have the onload JS disable the blur?
- DagAgren 7y agoDo modern browsers even support progressive display of JPEGs? I have not seen it happen in years and years.
- dspillett 7y agoI expect they do. It might just be that people aren't often encoding JPEGs such that they can take advantage of the support (or that your link is generally fast enough that you don't see an intermediate render of the data).
- jrochkind1 7y agoYes.
- londons_explore 7y ago
- BeeOnRope 7y agoThe examples discussed seem to assume a dynamic site where you load the images (and the blurha placeholder) from a database, but how easy would this be to use for a static site with large images, perhaps embedding the blurha directly in the HTML?
- asadkn 7y agoThis looks great. For browsers, would have been even better if the result was an SVG or pure CSS gradient. Canvas can still have performance hit on mobiles. The JS implementation of Gradify [1] did something similar but by using CSS linear-gradient which is even better in terms of performance. I wonder if the same could be implemented here. [1] https://github.com/fraser-hemp/gradify https://github.com/fraser-hemp/gradify
- DagAgren 7y agoYou can decode using a very small canvas to minimise performance issues, and the scale the resulting image up.
- vz8 7y agoThe gradify example site[1] has been taken over by a weight loss promotion. Here's a fork of gradify[2] with examples. [1] http://gradifycss.com/ http://gradifycss.com/ [2] https://github.com/QueraTeam/gradify https://github.com/QueraTeam/gradify
- WorldMaker 7y agoYou don't have to keep it displayed on canvas given it isn't "animated". You can use temporary canvases for the initial render then convert it to a Blob (and from a Blob to an Object URL). Most browsers at that point use the GPU's transforms to quickly convert the image data to a basic compressed JPG or PNG and then the usual browser performance mechanics of an image apply (and as a URL you can use it in regular IMG tags or CSS things like background-image). Presuming you remember to revoke the Object URLs when you are done with the hashes to avoid Blob leaks, performance is improved over the "simpler" canvas techniques that just display the rendered canvases. If code helps, here's how it looks as a React Hook in Typescript: https://gist.github.com/WorldMaker/a3cbe0059acd827edee568198376b95a https://gist.github.com/WorldMaker/a3cbe0059acd827edee568198... (I offered the code to the react-blurhash repository in its Issues.)
- londons_explore 7y agoThe cost is in the initial render to a canvas. Once on a canvas, there is no advantage to converting it to a blob for display in an IMG tag as far as performance goes.
- speedgoose 7y agoIt's cool, I have no use for it but it's cool.
- ampdepolymerase 7y agoWhat's the advantage of this over using a Gaussian?
- DagAgren 7y agoThe amount of data to send is miniscule.
- DagAgren 7y agoHello, I am the main author of this. Also make sure to check out the README on Github for more technical details! https://github.com/woltapp/blurhash https://github.com/woltapp/blurhash
- animalnewbie 7y agoVery nice work. Appreciate you open sourcing this
- JMTQp8lwXL 7y agoOn the demo site (https://blurha.sh https://blurha.sh), if you go down to Components (where there's two input fields, separated by an X) if you type '99' and '99' the website becomes noticeably unresponsive. Besides this very small bug, very useful piece of technology!
- marcusjt 7y agoI'm surprised this isn't more web focused, i.e. a NodeJS library which encodes an image as a BASE64 data URL which any browser can render immediately while the real image lazy loads
- DagAgren 7y agoIt's a lot more efficient to do it once, and store it. Also, it's really simple! Go ahead and create a library for it! The algorithm is tiny, and easily ported, so it's quite fun.
- nojvek 7y ago100% agreed. A base64 encode would be a lot more browser friendly. Something like `background-image: url('data:image/jpeg;base64,....')` means you only need the encoder, the browser would decode without any js being needed (which would block initial page render)
- Scaevolus 7y agoFor comparison, Instagram sends ~200 byte base64 thumbnails in its API responses. It's a low-res JPEG with a fixed header to save a bit more space. This is also used to blur sensitive images. You can get even better results with webp. It requires minimal javascript support-- just enough to build an img tag with the appropriate data url, and renders just as fast as any other tiny image. Upscaling blurs away the imperfections. https://stackoverflow.com/q/49625771 https://stackoverflow.com/q/49625771
- alephnan 7y agoCan you clarify the significance of the fixed header?
- metalliqaz 7y agoif the header never changes, it doesn't have to be stored or transmitted. the client-side code can simply prepend the header after getting the data.
- Scaevolus 7y agoJPEGs were never meant to be <1KB, so the header can be rather large-- looking at one example, the truncated JPEG data Instagram sends is 248B, and the additional fixed header information is 607B, resulting in an 848B thumbnail (the truncated data includes dimensions). That 607B of fixed header includes some compression settings that don't need to change between images: - 132B of quantization tables, saying how much to divide every coefficient by-- for subtle details the difference between "128" and "129" doesn't matter, and you can just send "2 * 64". - a 418B Huffman table, which stores the frequencies of different coefficient values after quantization-- small values are much more common than large ones, given the divisors. You can have smaller image data with properly tuned tables, but that only matters for large images. For 250B of image data, it's a waste. - Miscellaneous other details that are fixed, like the file type magic headers, the color space information, and start/end markers. You can dig in more by putting a thumbnail like https://i.imgur.com/M0EsJH1.jpg https://i.imgur.com/M0EsJH1.jpg into this site: https://cyber.meme.tips/jpdump/ https://cyber.meme.tips/jpdump/
- 7y ago
- 867-5309 7y agoany ideas how this would impact on SEO/Pagespeed? if you are doubling the amount of pictures being loaded, albeit a tiny placeholder for a thumbnail - not from a size perspective, but from a network perspective surely this would double the amount of image fetches
- DagAgren 7y agoThe whole idea here is that you do not need to fetch an extra image. The image is generated in code on the client side from the short string. So there is no network connection needed, and the placeholder is there instantly.
- deleted 7y ago[deleted]
- krossitalk 7y agoInteresting bug on the demo page: - Upload any image - Now upload a transparent png - It does not clear the previous image, layering them together - It hashes the combination picture. Super neat!
- dang 7y agohttps://wolt.com/blog/hq/2019/07/01/how-we-came-to-create-a-new-image-placeholder-algorithm-blurhash/ https://wolt.com/blog/hq/2019/07/01/how-we-came-to-create-a-... is related. Via https://news.ycombinator.com/item?id=20339442 https://news.ycombinator.com/item?id=20339442 (1 comment).
- busymom0 7y agoFunny coincidence that I am working on an iOS app and was looking for something similar just couple days ago. I ended up building something but your swift solution seems much better than what I have so I will switch to yours. Thank you!
- DagAgren 7y agoNice!
- deleted 7y ago[deleted]
- mneubrand 7y agoI implemented something similar when I was playing around on a personal page. It's built by Jekyll and inlines a base64 version of the jpeg with a CSS blur filter, then lazyloads the real image and transitions it in via opacity 0->1. You can see it in action here: https://www.road-beers.com/ https://www.road-beers.com/
- rgovostes 7y agoThe accompanying blog post is unfortunately unreadable with an ad blocker on that blocks third-party trackers from Google Analytics, Facebook, and New Relic. It hijacks scrolling and continually scrolls back to the top. (Mac Safari + 1Blocker + PiHole.)