Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

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.




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

Search: