Hacker News new | past | comments | ask | show | jobs | submit login
Show HN: HN Avatars in 357 bytes
701 points by tomxor on March 14, 2022 | hide | past | favorite | 493 comments
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



This is fun.

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.

    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;

            if (s) {
            for (
                let s = entry.target.innerText, r = 1, i = 28 + s.length;
                i--;

            ) {
                // xorshift32
                (r ^= r << 13), (r ^= r >>> 17), (r ^= r << 5);
                const X = i & 3,
                Y = i >> 2;
                if (i >= 28) {
                // seed state
                r += s.charCodeAt(i - 28);
                x.fillStyle =
                    '#' + ((r >> 8) & 0xffffff).toString(16).padStart(0, 6);
                } else {
                // draw pixel
                if (r >>> 29 > (X * X) / 3 + Y / 2)
                    x.fillRect(p * 3 + p * X, p * Y, p, p),
                    x.fillRect(p * 3 - p * X, p * Y, p, p);
                }
            }
            }

            entry.target.prepend(c);
        } else {
            if (entry.target.firstChild.tagName === 'CANVAS')
            entry.target.firstChild.remove();
        }
        });
    },
    { rootMargin: '0px 0px 0px 0px' }
    );

    document.querySelectorAll('.hnuser').forEach((user) => {
    observer.observe(user);
    });


Thanks, Interesting idea. I'm a little sceptical of the performance improvement, but I suppose that depends what we mean by performance.

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


I wonder how `<canvas>` would compare to `<img src="data:image/svg..."/>` instead


Yes! it did cross my mind, I was just more familiar with canvas API so I cracked on with it... but I suppose encoding a simple format like bitmap would be trivial, almost like being able to access the canvas pixel buffer but with a lower overhead.

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!


Speaking performance/optimisations, interesting choice to destroy canvas when it leaves viewport and recreate it upon each re-entry.

Personally I'd leave it created and stop observing the element instead, à la

    /* … */ if (entry.isIntersecting) { observer.unobserve(entry.target); /* … */ }
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?)


That's a really good idea. Definitely something to measure, but I suspect you're right.


If it turns out there’s some upper bound where caching canvases in memory becomes a problem (eg large threads become not paginated), it’s pretty trivial to build a cache around [Weak]Map and get the ~best of both worlds.


This is great, thanks. A small fix I made to stop console warnings was to change:

  x.fillStyle = '#' + ((r >> 8) & 0xffffff).toString(16).padStart(0, 6);
to:

  x.fillStyle = '#' + ((r >> 8) & 0xffffff).toString(16).padStart(6, '0');
as the pad target length is the first parameter of padStart.


A better optimization would be adding appropriately sized SVG <use> tags to comments before first paint (to prevent re-flowing the layout) and lazily generate avatars to a SVG sprite sheet. No canvas required.


A nice improvement! An unfortunate side-effect of it is that it introduces some jitter during macOS's smooth scrolling in Firefox at time of first render, though.


Does the canvas need to be created and removed as it comes in and out of view? Using requestAnimationFrame() might also further improve responsiveness.


Does the canvas need to be created and removed as it comes in and out of view?

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.


Or just generate them on page load and call it a day. No bugs, no complexity, no edge cases.

Could it be faster? Sure, but should it be faster? Not really, IMO, the avatars load very quick, even on a big page.


Awesome! I did not know about IntersectionObserver. Will def add this to my toolbox! Thanks!


Hah! I got a really cool little icon that looks almost like the cassette tape pirate logo of Piratbyrån back in the day. I have old t-shirts with that logo.

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.


I made a chrome extension of this! So it won't disappear when you refresh the page: https://github.com/ekofi/hn_avatar_ext/


Works great, thanks


You are welcome :)


> Hah! I got a really cool little icon that looks almost like the cassette tape pirate logo of Piratbyrån back in the day.

I think it's a skeleton holding his arms up!

edit: Looks like I'm some green bug-monster


>MAKE. THIS. PERMANENT!

I second this. (Also I wanted to see what my avatar looks like, sry)


Truly unacceptable. (I look like an alien head!)


But Space Invaders is a classic!


Only commenting so I can see my avatar...


How dare you, you cyan flower vase on wheels :) I'm a spiky small yellow watchtower, yay.


And me? A green butterfly?


Yup. Or a trophy/ a goblet maybe? Not sure if green stands for 1st, 2nd or 3rd price though, up to you to decide :)


hi everyone :) I am a happy jumping frog :)


Commenting to see mine. Edit: Excellent! It's a blob.


Let's see what mine is Edit: I love it! A little bug :)


Looks like Shrek's forehead. Posting for my own avatar reveal...


I am an inverted android?


This should be pretty easy to make into a userscript if browsers still support that. I haven't checked on that in half a decade or so

also i wanted to see mine too


This is fun, and can easily be added as a userscript. I might look to do that a little later.


// ==UserScript== // @name HN icons // @namespace https://news.ycombinator.com/* // @version 0.1 // @description try to take over the world! // @author some people on HN // @match https://news.ycombinator.com/* // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @grant none // ==/UserScript==

(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;

            if (s) {
            for (
                let s = entry.target.innerText, r = 1, i = 28 + s.length;
                i--;

            ) {
                // xorshift32
                (r ^= r << 13), (r ^= r >>> 17), (r ^= r << 5);
                const X = i & 3,
                Y = i >> 2;
                if (i >= 28) {
                // seed state
                r += s.charCodeAt(i - 28);
                x.fillStyle =
                    '#' + ((r >> 8) & 0xffffff).toString(16).padStart(0, 6);
                } else {
                // draw pixel
                if (r >>> 29 > (X * X) / 3 + Y / 2)
                    x.fillRect(p * 3 + p * X, p * Y, p, p),
                    x.fillRect(p * 3 - p * X, p * Y, p, p);
                }
            }
            }

            entry.target.prepend(c);
        } else {
            if (entry.target.firstChild.tagName === 'CANVAS')
            entry.target.firstChild.remove();
        }
        });
    },
    { rootMargin: '0px 0px 0px 0px' }
    );

    document.querySelectorAll('.hnuser').forEach((user) => {
    observer.observe(user);
    });
})();


the header formatting is broken. You should make a think on pastebin instead



Works a treat. Thanks, weird semaphore thing!


How dare you! Edit: ooooh, mine's a beetle!


Perhaps I'm a little fighter jet from an 8-bit arcade?


Or a guy with a tophat! Dig it.


Yours reminds me of the Wild Hunt from The Witcher, at least the crown


i love it, its very cute, reminds me of the old MSX


I do feel like HN needs avatars of some kind... just don't know if they should be customizable or fixed (like this one).


With how pretty much every platform has "avatars", that the user can customize, I think it would be fun just getting one assigned to you.

Edit: Huh, I got an alien house. Reminds me of some game.


Definitely not customizable, that's chaos.

Unique, fixed little color doodads make it easier to see who's who in a thread.


I'm for the fixed avatars. We are all born with certain unchangeable aspects.


Hey man, we were just discussing avatars, not trying to create an existential crisis.


Looks like I got blue transformer/reinhardt


It should be fairly straightforward to turn this into a browser addon, give users a choice and offload the people behind HN.


Or even easier to make a userscript: https://openuserjs.org/


  // ==UserScript==
  // @name        HN Avatars in 357 bytes
  // @description Annotated avatars on all HN comments.
  // @author      tomxor (https://news.ycombinator.com/user?id=tomxor)
  // @namespace   https://news.ycombinator.com/item?id=30668137
  // @include     https://news.ycombinator.com/item*
  // @include     https://news.ycombinator.com/user*
  // @grant       none
  // @version     1.0.0
  // ==/UserScript==

  [js goes here]



What is a user script?


A JavaScript script injected into a website by the user (as opposed to the website).


I see what you did there ;)


Could we just add it to HN Special? https://github.com/gabrielecirulli/hn-special


Doesn't work on FF does it?


Trying mine.


Trying mine as well


Trying mine.


can't resist, trying mine...


Trying mine.


Trying mine…


ok trying


mine as well


mine toooo


me too

edit: ooo, i'm a...green house?


me too


me too too

edit: oh that was unexpected


Test 421

edit: A bell? A thicc rocket..? Neat.


I'm gonna go ahead and vote no on this.


But your avatar looks like a Ninja Turtle with shades on. You should be enjoying this. :)


Wow, I thought it was a green camper van!


Likely still traumatized by existence of new reddit UI. Old appears to be better.


I just find pfps visually distracting and enjoy HNs stance as one of the few remaining sites keeping an old school look. I feel like it would affect the tone of the site and negatively affect the quality of the conversation.


You got Cthulu!


And I think yours is an upside-down helicopter!


It almost looks like a minimalist wolverine! [1]

[1]: https://i.pinimg.com/736x/2b/e7/cb/2be7cb420de47ac68e1345f4a...


For better rendering on high-DPI screens and (more subjectively) screens of people that have zoomed in because HN’s text sizes are unreasonably small, add c.style.imageRendering='pixelated'.

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


It's a good point, I did consider using that feature but the problem is it's not very cross browser friendly, so I stuck with simpler DIY pixel scaling.

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...


Firefox shipped the value "pixelated" five months ago, so you don’t need to worry about a "crisp-edges" fallback any more. Safari, back in 2016. https://caniuse.com/css-crisp-edges

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.


Ah, good to know it's finally normalised.


Reply here if you just want to see how your username looks like, so we avoid noise and keep that Show HN on the top. comments are really interesting


Or change document.querySelectorAll('.hnuser') at the beginning to document.querySelectorAll('.hnuser, #me') so it will also show up beside your name when logged in at the top.


Fire! Fire! Help me! 123 Cavendon Road. Looking forward to hearing from you. Yours truly, Maurice


I'll add that this will work on your profile as well - basically anywhere with an anchor to "news.ycombinator.com/user?id=blahblahblah"


Thanks. If you input the code multiple times it shows you a number of avatars equal to the number of times you input the code.


I am interested to see what my avatar looks like ;) edit: aha, squid!


I did something similar to this in Racket to generate unique filetype icons for unknown files. If you're careful with your defaults it can work really well.


Mine is like a sad face with a big weird nose


I guess you can probably make some personality (or mood) tests with them, as this is not, what I see in your avatar ...


Checking.. huh, looks very different from most other generated avatars.. almost inverted from the look most of them have.


I don't actually like my avatar very much, lol.

I can't decide whether it's a moustache and glasses or a bikini.


I see a bulldog with a flat top haircut (or old school phone) on it's head.


I'll take that over my moustache bikini :P

Thank you bird man.


I see DrDisrespect


So many comments on this thread.

edit: I AM THE CACTUS MAN


I like the idea, hope someone makes a userscript. Edit: my yellow avatar was a bit hard to see, though


Mysterious person in a hat and trench coat.

Perfect.


Wish I was smart enough to do this


I know I can modify the query, but I'm gonna join the train :) EDIT: A happy knight!


Mine seems to be a green… trident of some kind, maybe? I don’t even know what that is.


Let's see what I got

Edit: aha, helicopter


I hope mine's purple. Huh, it's a like a pale ceiling death laser.


test


Naboo fighter checking in.


oh man thats not fair


Sad avatar checks out


Hey look, I got a funnel.


Test post please ignore!


Thanks! Wanting to see what mine would look like. :)


Mine looks like a chest or robot cyclops, I like it.


nobody would dare to do this if HN had notifications


I love this <3


I'm just here so I don't get fined


I'm guessing mine is orange in color!!


F


Mine will be the orange one because I look good in orange.

[Edit: F!]


Bippity boppity this avatar is my property


Testing mine

Edit: a poker face with a strange hat, not bad


test


Thanks, I felt bad about spamming here.


I wonder what mine will end up being


That's definitely a UFO


Yours is also a UFO.


please be good... edit: its a turnip


FOMO Nothing to see here move along


I hope mine is a spirit animal :-)


Can I see mine please? Yes I can.


after reminding people not to paste code into their console, i pasted code into my console


I want to see what I look like :)


Testing. Testing. This thing on?


Thanks for grouping all these.


Let's have a look then.


Ok, sure. Let's try it.


Hope it's spectacular.


Wonder what I've got.


nice thread, i just spent 30 minutes looking at all the little avatars


Am I the Lich king now? :)


Hoping for something cool!


Hoping for something nice


Just checkin for fun :)


How do I look here? :D


big fan of single quotes, I guess I don't hate mine


This is pretty fun.


Such a great idea.


Bonjour le monde!


Howdy Ya'll!


I'm curious!


Wow, what the...


let's see what the rng gods have for me


What... am... I?


Excellent idea!


Kilroy was here


Let's see.


Testing 1, 2...


test

edit - I'm an upside down android with antennae?


How we lookin?


Let's see


Hello, world!


Just testing.


Hello, world!


It's me!


I love this!


Same here :D


Nice!


please be a dog! [edit]: Damn :(


mine is giving me slight killroy vibes

love these


Beepa boops


Robot chef!


Hello there


Reply here.


Great idea.


So excited!


And i just can't hide it!


:D


Test


.


not sure how but here we go!


.


----


Test


Test!


Replying!


And mine!


Test!!


Test


Test!


Very cool.


Here

Buffalo Cyclops Head?


Thanks!


Testing :)


Woop woop!


Test


:)


Testing.


Yay me


Hi


My avatar


Thanks!


:)


.


Checking.


1-2-3


Check 123.


Thanks!


+


?


.


Test


I do!


.


.


.


Hmm


Test


Test post


Thanks


Hello


:) ty


Test


.


:-)


Hello!


:)


Thanks!


.


Yours looks like a spy.


and my avatar reveal is...


thx, i'm coming now


well i guess i gotta see


test post please ignore


beetle warrior fuck yea


oh, how does it look?


test, please ignore


this is a cool idea


what is my purpose?


oh wow, I love it! It's like an octopus alien!


commenting to see


interesting...


testtesttest


let it roll!


hello, world


wello horld


hello world


am i lucky?


view avatar


check


test


roll


roll


yeeehawwww


hm


hmmm


test


thanks


sweet


test


beep boop


testing...


testing


sup


test


yeahh


testing


curious


oo


blah


beep boop


ty


blah


replying


hey!


wagwan


hello


lol


checking


test.


hello


yarg


yay!


hello!


test


hi


hello


yours is cool


hi!


cool!


test


yo


test


ty


test


test


thanks :)


whoami


boop


test


hello


noise


test


test


boop :)


beep


test


test


Let's see


test


thanks


test


test


what is this - a spaceship?


test


test


I guess I can drop my test way down here, too!

Edit: A head thing, I guess? Or a flower?


testing on the test


These are very cute, but I'm mildly annoyed that mine is basically invisible


It's all about perspective... literally; You're just a dark mode deity. https://darkhn.herokuapp.com/item?id=30668137]


That sucks.

I was thinking of ways to prevent that, but without adding a bunch of ifs it kind of kills the minimalistic approach of the original code.


You can have an outer loop that checks how many pixels are set in the avatar and keeps trying to generate a new avatar with a different seed until it's not (rejection sampling). As long as the probability of the avatar being too empty is small it should be fast.


The main problem is probably the color, maybe somehow capping the maximum brightness would be doable.


looks a bit like old Prague Metro logo, cool

https://commons.wikimedia.org/wiki/File:Praha,_Masarykovo_n%...


actually your avatar looks like a mildly annoyed raccoon!


You do stand out now!


Ive been a dev for over 2 decades but I still cant grok bitwise operators - anyone have a reference that may help with that?


<< means shift left (moves the bits to the left side)

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


Right shift can get tricky for negative numbers. There are two operators for it, one which respects the sign bit, the other does not:

- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...


Testing…


I struggled with this as well. Now I'm a bit rusty as I haven't used it for a while, but what really helped me was to start thinking about binary numbers more as strings. It also really helps me to visualize binary operations in binary space, not hex or decimal. That's still very confusing to me.

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.


> There's some wrapping behavior of course. [...] the 1 would teleport to the right side.

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.


> It also really helps me to visualize binary operations in binary space, not hex or decimal. That's still very confusing to me.

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.


I've found the python docs[0] to be quite helpful for this topic.

[0]: https://wiki.python.org/moin/BitwiseOperators


Thanks, but its still a bit confusing for me. Im looking for a really dumbed down kind of approach: input, operator, visual explanation of what its doing, output. Im a really bad learner, and didnt study computer science or anything. Ive never had a reason to touch binary, so find these operators unintuitive.


I think before you can understand binary operators, you’ll need to understand binary itself and how to convert it to and from decimal. This crash course computer science video may help: https://youtu.be/1GSjbWt0c9M

In fact the whole course might be useful!


I understand the conversion and representation - i guess I just dont understand the intent behind the usage of these operators.

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.


There's many reasons for bitwise operators to exist.

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 wiki is a visual representation. The representation is 0s and 1s.

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.


> 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?


Try thinking about the equivalent in decimal:

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)


If you don't know about the binary representation, you won't be able to learn binary operators. If you do, they're fairly straightforward. I'd suggest learning about binary first.


You'll just have to use it in actual programs to really understand it. You can't properly understand `if` and `for` either just by reading some doc page about it, you can't become a guitarist just by reading about technique, you don't become a carpenter just about reading about it, etc. So don't feel bad if you're confused by just reading that page.


Reading didn't do the trick for me. It clicked when I started writing code to accomplish tasks, like assignments or coding puzzles, that required bitwise operations.

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...


It helps if you look into bits and binary arithmetic; I learned it from the appendix of my school book Structured Computer Organization. Binary and binary arithmetic is probably the best place to start.


This was inspired by @frncsdrk's submission from earlier today [0]. The generative concepts used were inspired by and derived from dweets from @KilledByAPixel and @FireFly [1] [2] [3]

The 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:

    for (const u of document.querySelectorAll('.hnuser')) {
        const p=2;
        const c=document.createElement('canvas');
        const x=c.getContext('2d');
            c.width=p*7, c.height=p*7;
            u.parentElement.prepend(c);
        for (let s=u.innerText, r=1, i=28+s.length; i--;) {
            // xorshift32
            r^=r<<13, r^=r>>>17, r^=r<<5;
            const X=i&3, Y=i>>2;
            if (i >= 28) {
                // seed state
                r+=s.charCodeAt(i-28);
                x.fillStyle='#'+(r>>8&0xFFFFFF)
                .toString(16).padStart(0, 6);
            } else {
                // draw pixel
                if (r>>>29 > X*X/3+Y/2)
                x.fillRect(p*3+p*X, p*Y, p, p),
                x.fillRect(p*3-p*X, p*Y, p, p);
            }
        }
    }
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


There's actually a slight difference between the golfed and un-golfed version: the un-golfed adds the avatar to the beginning of the line with the username, whereas the golfed version adds it right next to the username. I actually find it to be more visually appealing at the beginning of the line, so I'm sticking with that in my Greasemonkey script.

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


Yup, i noticed an opportunity to make it smaller while golfing.

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.


I've never heard of dweets/dwitter before; thanks for sharing. For anyone else:

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/


further ungolfed:

  for (const huser of document.querySelectorAll(".hnuser")) {
    const s = huser.innerText;
    const p = 2;
    const canvas = document.createElement("canvas");
    const context = canvas.getContext("2d");

    canvas.width = p * 7;
    canvas.height = p * 7;

    huser.parentElement.prepend(canvas);

    {
      const xorshift32 = (n) => {
        n ^= n << 13;
        n ^= n >>> 17;
        n ^= n << 5;
        return n;
      };

      const seedSteps = 28;

      let seed = 1;
      for (let i = seedSteps + s.length; i >= seedSteps; i--) {
        seed = xorshift32(seed);
        seed += s.charCodeAt(i - seedSteps);
      }

      context.fillStyle =
        "#" + ((seed >> 8) & 0xffffff).toString(16).padStart(0, 6);

      for (let i = seedSteps - 1; i > 0; i--) {
        // continue the seed
        seed = xorshift32(seed);

        const X = i & 3;
        const Y = i >> 2;

        if (seed >>> (seedSteps + 1) > (X * X) / 3 + Y / 2) {
          context.fillRect(p * 3 + p * X, p * Y, p, p);
          context.fillRect(p * 3 - p * X, p * Y, p, p);
        }
      }
    }
  }


You're off by one in the initial seed step. Change

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.


Your minified version lacks the .padStart(0, 6) you have in this comment, without which colour selection fails in just under 1⁄16 of cases, yielding black. (Or in just under 1⁄256 of cases, a semi-transparent colour!)


Thanks, I know it's absent (it's golfed not minified), which means I manually manipulated it to make as small as possible which sometimes means making "concessions" where things are not ideally preserved - But in retrospect you're probably right, I was being too aggressive and should have either left the padding in or replaced it with some guarantee of formatting length length like "&FFF|0x111"


I've been following Frank Force (KilledByAPixel) for quite some time now, he's on the forefront of generative design via HTML canvas.


Yeah! he's done a deep dive into it more recently. Attempting to follow his twitter thread at the moment is like trying to consume a fire-hydrant of generative art :D hard to keep up, but looks amazing.


Looks way better if you add style="image-rendering: pixelated;" to the canvas. Crisp edges on every display.


It does! Here's a bookmarklet with that added (generated here: https://caiorss.github.io/bookmarklet-maker/)

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


Yup, definitely second that. With that change I can definitely imagine this as a user option in the settings. Reading is so much easier.

(also wanted to see my avatar)


Wow this is really great! Noob question, but let's say I wanted to permanently load this JS in my browser? How would I achieve that? Grease monkey? Extension? Thanks :)


There's a Wikipedia page greasemonkey-like things: https://en.wikipedia.org/wiki/Userscript_manager

Might try this one: https://chrome.google.com/webstore/detail/violentmonkey/jinj... I just installed it and it seems to work well.


Why not create your own web extension? It will take just a few lines of code, you can look at this example for reference[0] (also on Github[1]).

[0]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Web...

[1]: https://github.com/mdn/webextensions-examples/tree/master/bo...


I don't know about you, but I really don't want an extension for every small change one might want to apply to one specific site.


That's fair how about Tampermonkey -- I use it occasionally to apply js/css/html fixups to sites, e.g. Add browser titles to sites for better bookmarks, re-organize/add site html, etc. I did use it for removing content but ublock origin is much better imo for most cases.


Violentmonkey is a nice open source alternative to Greasemonkey.


Your wording implies Greasemonkey is not open source. I don’t know if that was your intention or not, but Greasemonkey is open source.


They're probably confusing it with Tampermonkey, which isn't open source.


You can quickly create a Chrome extension to excecute js or css on any website: https://developer.chrome.com/docs/extensions/mv3/content_scr...


Should be really easy with grease monkey. I simply pasted the snippet into a new script and enabled it for this site. Works perfectly out of the box.


needed to add.

  let c,x,X,Y,r,i,s;


and let u;


Or ask pg to save it on the server side.


I wonder how easily you could have slipped in an obfuscated cookie stealer


HN sets one cookie and that is "httpOnly" and "secure", so it can't be read from JavaScript. You can test this yourself by typing document.cookie into your console, it should return an empty string.


Given that it takes about 20seconds to read through the snippet, not easily ;-)


I'm trying to do this in Firefox 98.0 and I'm not able to. I see this in the console:

> 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!


UTF-encode the maliscious snippet and disguise it as the regex pattern haha


Doesn't UTF-8 just give you the exact same letters, since it's already ASCII?


I think it was meant to be UTF-8 escape codes, as in \u0000 etc.


Pedantry: These are really Unicode escape codes not UTF-8 escape codes, since they're codes for Unicode code points. You can write invalid UTF-16 with these escapes (because unlike UTF-8 the UTF-16 encoding actually borrows Unicode code points that are not scalar values, called the "surrogates") but I don't know what the standard says should happen if you do that.


Ahh that makes sense, thanks.


I've got a pooping person hm


I seem to be the Github Octocat


I didn't see it as that until you mentioned it.


You got a piranha plant.


...and I have... um... an octopus carrying a block of wood?


it's a single eye, squinting, either in concentration or distrust


I think you have a storm trooper.


You have some very intense eyes.


Yeah I thought it was a tribal ritual man sort of thing... but now that you mention it...


I have opened a merge request on Refined Hacker News (https://github.com/plibither8/refined-hacker-news) which is a pretty great HN extension for Firefox and other browsers. Thank you to OP

Edit: I'm pretty pleased with my blue Space Invader!


It would be neat if the code also applied to the username in the top right so I didn't have to make a spammy comment to see what it gives me. ;)


Just replace the '.hnuser' bit with '.hnuser, #me'


Just F12 and manually edit some username, after that re-paste the code and you will see the avatar.


The other way is to just navigate to your own profile page and apply the script there.


People could carefully choose their username to match a desired avatar!

It could be a fun coding challenge to try to generate usernames which match a relevant avatar ;)


Reading the comments, this feels like a giant Rorschach test for HN...


This is actually a really useful feature. Makes it easy to see at a glance who is interacting with whom in a thread.


Got me to post for the first time just to see my guy.


You could have stayed in stealth mode by just going to your profile page ;)


Mine is a skull with a top hat, so naturally I demand that this is made permanent. ;)


This is a class of "sprite" generators, which I find fascinating. This one in particular seems very good, although I wonder how much of that is our brains* filling in context.

*I am, evidently, a mushroom cloud


My sense of wonder is back! Please make this permanent.


I would _love_ to have this (as well as dark mode and a few CSS tweaks for better line wrapping) baked in to the main site, as 2020-era options.


Fun showcase, but truthfully I would not want this to become a permanent feature. A browser plugin would work.


As others mentioned, you could add to the plethora of HN browser extensions. Another idea would be to allow people to specify an avatar in their bio and load it in, though that would entail a lot of web requests I guess. Also, I just realized that it's not random... wow!


Cool! Works on mobile, too. Prepend "javascript:" to the code and enter on search bar.


Not working for me on mobile. I tried both the extension and the greasemonkey script ???


I made a chrome extension of this! So it won't disappear when you refresh the page: https://github.com/ekofi/hn_avatar_ext/


Wouldn't it be nice to generate the colour in HSL and/or clamp/normalise the luminosity so it's always darker than the background and prevent "invisible" avatars, such as the one generated for @lexicality?


This is really cool!

I've coded in JS for years, and I don't understand how these bit-shifting operations become pixel avatars, or what the magic numbers represent and why they were chosen.

I would like to understand how the author thinks. :)


I like it!

Btw. if you have browser extension installed, that add custom scripts to websites, like greasemonkey or tampermonkey - you can have those nice avatars all the time. (without opening up the console)


Is there a screenshot of that in action for mobile users?



This is interesting :D

Setting it as

  javascript:<code>;void(0); 
can make it useful as a boomarklet :)


Point of information: the ;void(0); isn’t necessary here because the code ends in a for loop, which produces undefined.

(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):

  data:text/html;charset=utf-8,<a href="javascript:'<h1>Here is a page.'">α</a> <a href="javascript:({toString(){return '<h1>See, stringification!'}})">β</a> <a href="javascript:/*even*/null/*gets stringified*/">γ</a> <a href="javascript:undefined">δ</a>
.)


It wasn't actually working for me; it was returning 0 actually. But yeah, this is a good point as well. :D


Huh, turns out my understanding of how it worked was wrong: in things like for loops, it takes the last value encountered. I should have anticipated that, really, given that “last expression” isn’t generally a thing in JavaScript anyway (it’s statement-oriented, not expression-oriented), so javascript: URIs (and I guess on* event handler attributes too, I know they’re a bit weird too) were clearly doing something clever and not-usual-JavaScript. I should delve into specs until I can find it; it seems like it’s at least mildly expression-oriented in character.

I retract the first paragraph of my earlier comment with an apology.


I just put onion2k's version as a script in violent monkey, set to run only on NH.


Mine is barely visible so the colors may need some work, but I love the simplicity of it.


Pretty damn cool, would be a nice feature :D

EDIT: Mine looks like some tentacle-headed creature


How can I try this on mobile?



Haha this is awesome, rolling...

Looks like a Funnel or Chat bubble with the letter V in it.


Genius. I hope this whole "foo in bar bytes" thing catches on!


This is awesome and an excellent way to start my day, thank you !


now we need the backwards version, draw your icon, get your name


That's a neat little snippet. Thank you for sharing it. :)


Let's give it a try! EDIT: A purple girl with braids :)


This is cool. Should totally be part of official HN js


Should be an unlockable feature after 100 karma through a checkbox in your profile


Quite nice. Wish mine was a bit more visible though.


Very, very nice from a code golf perspective though.


What would be the Python, server-side version of it?


Cool! Just commenting to see what I've got xD


A lobster!


I see a bull in a suit.


This is real neat. Wanna see what mine looks like


Ooooh what will I get

Edit: hmmm, kinda looks like a Tori gate.


Mine looks like an ovary, neat idea tho!


This is neat! Commenting to see my own.


This is cool! Turn this default please!


huh. is this something we can control?


In a way, yes. But from the way you phrased it, it sounds like you believe it is a HN feature, which it isn't. It's javascript running in your browser that dynamically generates the avatar via canvas2d and adds it wherever it finds the .hnuser class.


ahh I see. Thank you.


I wonder what mine looks like.


Very cool, thanks for sharing!


Very cool, thank you for this!


gentle reminder not to paste internet code into your browser console


Firefox made me copy and paste "allow pasting" to allow pasting in the console.


I want to see my avatar.


That is awesome, wow!


This is awesome hahaha


How do I buy the NFT?


That is pretty cool.


How does this work?


This is really cool


Love this.

Posting to see what I got.


Must…check…avatar!


What's mine?


Mmmh, definitely looks like a kid's dick drawing


looks like an ant's head


Does yours look better than mine?


This is so cool!


Looks very cool


This is cute!


Test


Test


Brilliant!


Cool!


test. to view my avatar


got to see my avatar!


love my little alien.


checking my avatar


what is my avatar


also testing mine


this is so cool


cool


check 12


testing!


test


cool


test


test


hmmm


cool


How did you get this idea?


my icon check


Cute, but I don't think it really adds much to the site. The content of the comment is much more important to me than whoever posted it. In a way, even the usernames are superflous.


Try looking at one of the threads in this post, I'm struck by how much easier it is to follow a discussion at a glance. You see pink avatar talking to green, pink, green, oh here comes orange. You immediately know someone new jumped in with new info.

Also yours kinda looks like a portrait of a middle aged 8bit character.

Edit: Gotta admit though, on the front page it looks kinda goofy.


For me the use case is: someone leaves a comment that's ambiguous, someone asks for clarification, some people respond _what they think that original commenter meant_, finally originally commenter solves the doubt.

I find myself Control+F the name of the original commenter to see if he/she has replied, because I'm interested in _that_ response — not so much of the interpretation of others.


> The content of the comment is much more important to me than whoever posted it

I agree with this sentiment. I mostly did this for fun, however I will point out that occasionally people get mixed up about who they are responding to with regard to which comments in a thread came from which author, and a random avatar can help to prevent those mistakes.


An unkown person asks you to inject code in your browser.

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 running in a sandboxed environment(the web browser). The worst-case scenario is it gaining access to my HN account, and I don't really care that much about it.

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.


Even when obfuscated, the code looked safe to me. (But I did wonder, and thankfully, HN has set httpOnly on the cookies.)


It is very short and quite easy to see that it is safe, even if you do not fully grok what is going to happen. If you don't, then by all means do not run it.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: