Show HN: HN Avatars in 357 bytes
Paste the following into the console of any HN page - for annotated avatars on all HN comments. (self contained code)
for(u of document.querySelectorAll('.hnuser'))for(u.prepend(c=document.createElement('canvas')),x=c.getContext('2d'),c.width=18,c.height=14,s=u.innerText,r=1,i=28+s.length;i--;i<28?r>>>29>X*X/3+Y/2&&x.fillRect(6+2*X,2*Y,2,2)&x.fillRect(6-2*X,2*Y,2,2):r+=s.charCodeAt(i-28,x.fillStyle='#'+(r>>8&0xFFFFFF).toString(16)))r^=r<<13,r^=r>>>17,r^=r<<5,X=i&3,Y=i>>2
535 comments
[ 3.3 ms ] story [ 388 ms ] threadThe concept is to use HN usernames as the seed into a deterministic avatar generator. This generator is built from the famously simple xorshift32 PRNG, which both provides a random variable for the image generator steps, and "pseudo-hashes" the seed string to provide the initial PRNG state using a non-linear step (adding each codepoint - which is likely not very robust against collisions compared to proper hashing algorithms, but is simple and good enough).
The image generation part is a probability distribution with mirrored pixels... specifically: r>>>29 > X*X/3+Y/2 where the left side is 3 of the upper bits of the PRNG state (providing a random integer 0-7), and the right side is the squared distance from the centre for X + the linear distance from the top for Y. i.e the further from the top centre the pixel is, the less likely it will be filled, but linearly for Y and squared for X.
Un-golfed version:
Was fun to play with, but also surprisingly helpful in following discussions.It's worth mentioning these were heavily inspired by three particular dweets by @KilledByAPixel and @FireFly:
[1] https://www.dwitter.net/d/3078
[2] https://www.dwitter.net/d/19786
[3] https://www.dwitter.net/d/23326
[0] https://news.ycombinator.com/item?id=30660316
To anyone interested, I've added links to how the un-golfed version looks with Dark Reader enabled[1] and how it looks without Dark Reader[2]. To clarify, I've set my zoom level for Hacker News to 150% as I find the default to not be conducive to reading.
---
[1]: https://i.imgur.com/hw9pn0l.png
[2]: https://i.imgur.com/z3jV2NZ.png
There is one advantage of the golfed version, which is that it will also inject avatars wherever it finds the .hnuser class, including on other pages.
Dwitter.net is a challenge to see what awesomeness you can create when limited to only 140 characters of javascript and a canvas. Give it a go!
https://www.dwitter.net/
for (let i = seedSteps + s.length; i >= seedSteps; i--) to for (let i = seedSteps + s.length - 1; i >= seedSteps; i--)
The original is not using the for loop in the usual way so i starts off one smaller than expected.
Might try this one: https://chrome.google.com/webstore/detail/violentmonkey/jinj... I just installed it and it seems to work well.
[0]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Web...
[1]: https://github.com/mdn/webextensions-examples/tree/master/bo...
> Scam Warning: Take care when pasting things you don’t understand. This could allow attackers to steal your identity or take control of your computer. Please type ‘allow pasting’ below (no need to press enter) to allow pasting.
Oh now that I'm reading it carefully, I see there's a way to enable it!
Setting it as
can make it useful as a boomarklet :)(For anyone who doesn’t know about the meaning of void in JavaScript: it’s a unary operator that ignores its operand and produces undefined. What is commonly spelled `void(0)` is briefer as `void 0`. Why don’t people just write `undefined`? For some, because it’s shorter, but mostly for historical reasons: `undefined` is a global property rather than a keyword, and until ES5 (a decade ago), that global property was writeable, so you couldn’t rely on its value because people might do crazy things. And if you’re wondering why a javascript: URL needs to produce exactly the value undefined: if it doesn’t, the browser will convert the value to a string and replace the page with that, loaded as HTML, as demonstrated by this (paste it in your address bar, you can’t click on data: URLs directly for security reasons):
.)I retract the first paragraph of my earlier comment with an apology.
I was going to say that the insertion of these 24 bytes allows you to save ten bytes elsewhere by halving the size of the image (making features one pixel in size, rather than two), but then you’d also have to set the doubled image dimensions, and that’d cost more than ten bytes.
(Me, I use HN at 120% on top of a native scaling factor of 150%, which is currently implemented as downsampling of 200% rendering because Firefox hasn’t got fractional scaling right yet under Wayland, and widget.wayland.fractional_buffer_scale has some fairly debilitating side-effects on popup windows including non-rendering and crashes, which is a pity because it improves rendering so very much.)
In fairness I think there is basically two different values for chrome and firefox, although I'm not sure about safari and cannot test it...
This is valuable for high-DPI and zoomed pages, to avoid bilinear or similar scaling; the matter of halving the canvas size was a red herring.
[0]: https://wiki.python.org/moin/BitwiseOperators
In fact the whole course might be useful!
Without stepping through each line in a debugger I literally have no idea what the ungolfed version of the main post is doing, or why its doing it - and this seems to be typical of my experience with bitwise operators.
But I will definitely have a look at the references you have given. Hopefully that will give me a more intuitive understanding. Thanks.
For instance it's extremely easy, when looking at the binary representation of a number to check the correct end of the byte and determine if a number is odd or even.
You can use bitwise operators to bit shift and check if a value is over a certain amount but seeing if there is any value after a bit shift.
there's tonnes of real world applications:
* Bit fields (flags)
As they're the most efficient way of representing something whose state is defined by several "yes or no" properties. ACLs are a good example; if you have let's say 4 discrete permissions (read, write, execute, change policy), it's better to store this in 1 byte rather than waste 4. These can be mapped to enumeration types in many languages for added convenience.
* Communication over ports/sockets
Always involves checksums, parity, stop bits, flow control algorithms, and so on, which usually depend on the logic values of individual bytes as opposed to numeric values, since the medium may only be capable of transmitting one bit at a time.
* Compression, Encryption
Both of these are heavily dependent on bitwise algorithms. Look at the deflate algorithm for an example - everything is in bits, not bytes.
* Finite State Machines I'm speaking primarily of the kind embedded in some piece of hardware, although they can be found in software too. These are combinatorial in nature - they might literally be getting "compiled" down to a bunch of logic gates, so they have to be expressed as AND, OR, NOT, etc.
* Graphics
There's hardly enough space here to get into every area where these operators are used in graphics programming. XOR (or ^) is particularly interesting here because applying the same input a second time will undo the first. Older GUIs used to rely on this for selection highlighting and other overlays, in order to eliminate the need for costly redraws. They're still useful in slow graphics protocols (i.e. remote desktop).
The use case for bitwise operations is to control the value of 1 bit.
Consider if you have a 0000 1000 value. It's mapped to hardware output so the 4th LED is on. Now you want to turn the 5th on, and the 4th off. You would use a left shift so the output becomes 0001 0000.
That's all there is to it. Bitwise operators operate on bits, not bytes. Because computers operate on bytes, and you can't address a bit - you have to modify bytes to accomplish your goal.
I think this may be the fundamental intent I was missing. How do people wrangle these things in their head! Is it ever intuitive, or are even the proficient externally visualising this stuff in some way?
A left shift in binary adds a 0 at the end which multiplies by 2 (binary = 2)
In decimal, the equivalent, adding a 0 at the end, multiplies by ten (decimal = ten)
For example left shift by 1 on this 8 bit number (decimal is 32)
00100000
you just shift all the numbers to the left
01000000
--------
There's some wrapping behavior of course. Like what happens if you left shift 3 here? If it's an unsigned 8 bit number the 1 would teleport to the right side.
00000001
--------
the logical operators work like this (read downwards)
00010000 AND
00010000 =
00010000
--------
00001000 AND
00010000 =
00000000
--------
00001000 OR
00010000 =
00011000
With this in mind, I found it to be a good exercise to implement common bitwise operators that work on fixed length strings with some tests against the real binary operators in your favorite language.
Yeah, I think thats a major part of the problem for me. When im looking at these code snippets (as per ungolfed version of the post in a comment below) applying the operators to integers i just cant understand what it is they are trying to achieve, so I am unable to grok the purpose/intent. Even with the explanations given here im not sure why its doing what its doing - and I dont seem to encounter that issue outside of bitwise operators.
Not really. Shifting by definition discards 1 bits once they pass either "edge" of the value. The wrapping you describe only happens with rotation operations. In the languages I know they don't have fancy short hand syntax like shifting does.
1 << 1 = 2 because (0001 << 1 = 0010 = 2)
3 << 1 = 6 because (0011 << 1 = 0110 = 6)
>> works in the same way
2 >> 1 = 1 because (0010 >> 1 = 0001)
The other operators are exactly the same you'd find in electronics (and boolean algebra):
~ is the NOT operator
& is the AND operator
^ is the XOR operator
Sorry, I don't have a visual representation for this (I haven't looked for a link) but I would strongly suggest to write it down on paper and do some small exercises (checking with the result then on your favorite programming language).
On a day to day basis, you'd likely not use these operators at all, but as soon as you start decoding protocols or working on a bit-level, those are super useful!
I would suggest trying to decode a bitstream protocol to really grasp the concepts: you'd use masks and bitwise operators a lot :)
---
Edit: some useful links I've found: https://www.interviewcake.com/concept/java/bit-shift
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
I can't find any of my undergrad coursework online but here's an alternative that looks like a nice intro https://web.stanford.edu/class/archive/cs/cs107/cs107.1194/l...
One suggestion.. rather than creating a canvas for each user using a querySelectorAll and a loop, I'd use an IntersectionObserver and only create the canvases as they scroll into view. That way the user's device won't need to create hundreds of elements when the code runs.
Not really. You could just create them and not bother removing them. You'd need to check if the user already had an avatar if you did that though, or you'd end up with lots of repeated avatars when you scroll up and down the page.
Could it be faster? Sure, but should it be faster? Not really, IMO, the avatars load very quick, even on a big page.
Personally I'd leave it created and stop observing the element instead, à la
Do you think (or know) that swapping observed node for canvas (potentially producing many canvases) is more expensive than keeping all observers and having smallest possible amount of canvases? (Maybe I'm biased towards saving CPU but saving RAM is better after all?)Your strategy essentially trades a one time computation and DOM mutation with a continuous but lighter one with some added overhead. In this case I suspect the total power performance is worse over time; _however_ latency and UX should be _better_, since it removes the relationship between total comments on the page and the time to render avatars, which is currently about 100ms for this page on my machine.
I'm definitely biased towards preferring a one time change and making it as efficient as possible, but when the input has a high enough ceiling I can see how your strategy would make more sense - I'm not quite sure where I stand on HN threads - but thankfully this is just a user script so we can make our own choices :)
This thread is a good perf test, I might give it a quick go.
Also as others mentions the pixelation style thing is now finally fully cross browser supported, which should make it easier to achieve nearest neighbour upscaling for url encoding.
[edit]
I underestimated how involved image format headers are!
Sorry folks, but this should be a red flag for all of you. I guess you all know enough about obfuscation to know that what you think the code does is not always what the code does.
"But it has upvotes!" For all you know, the script could upvote this post.
It's not going to install a bootsector virus on my machine, or ransomware-encrypt all the data on my NAS. So the risk of anything important happening is small, and the same risk as visiting any website on the internet.
MAKE. THIS. PERMANENT!
On the functional side, this really helps you see who is who in a long threaded discussion. Your eye is much quicker at following the little color icons than their names.
[1]: https://i.pinimg.com/736x/2b/e7/cb/2be7cb420de47ac68e1345f4a...
I second this. (Also I wanted to see what my avatar looks like, sry)
(function() { // Initial concept is from: https://news.ycombinator.com/item?id=30668137 (tomxor) // The script we are using is from: https://news.ycombinator.com/item?id=30670079 (onion2k) 'use strict'; let observer = new IntersectionObserver( (entries) => { entries.forEach((entry, i) => { if (entry.isIntersecting) { const p = 2; const c = document.createElement('canvas'); const x = c.getContext('2d'); c.width = 18; c.height = 14; const s = entry.target.innerText; const r = 1;
})();also i wanted to see mine too
I think it's a skeleton holding his arms up!
edit: Looks like I'm some green bug-monster
Unique, fixed little color doodads make it easier to see who's who in a thread.
Edit: Huh, I got an alien house. Reminds me of some game.
edit: ooo, i'm a...green house?
edit: oh that was unexpected
edit: A bell? A thicc rocket..? Neat.
Edit: hmmm, kinda looks like a Tori gate.
(also wanted to see my avatar)
javascript:(function()%7Bfor(u%20of%20document.querySelectorAll('.hnuser'))for(u.prepend(c%3Ddocument.createElement('canvas'))%2Cx%3Dc.getContext('2d')%2Cc.width%3D18%2Cc.height%3D14%2Cc.style.imageRendering%3D%22pixelated%22%2Cs%3Du.innerText%2Cr%3D1%2Ci%3D28%2Bs.length%3Bi--%3Bi%3C28%3Fr%3E%3E%3E29%3EXX%2F3%2BY%2F2%26%26x.fillRect(6%2B2X%2C2Y%2C2%2C2)%26x.fillRect(6-2X%2C2*Y%2C2%2C2)%3Ar%2B%3Ds.charCodeAt(i-28%2Cx.fillStyle%3D'%23'%2B(r%3E%3E8%260xFFFFFF).toString(16)))r%5E%3Dr%3C%3C13%2Cr%5E%3Dr%3E%3E%3E17%2Cr%5E%3Dr%3C%3C5%2CX%3Di%263%2CY%3Di%3E%3E2%7D)()%3B
https://gist.github.com/caseywatts/c0cec1f89ccdb8b469b1