Random paint effects - HTTP 203
Вставка
- Опубліковано 10 лют 2025
- In this episode Jake and Surma chat about creating 'random' paint effects, and why they shouldn't be truly random.
The original by George Francis → goo.gle/3gNvAPO
Houdini.how →goo.gle/3jxd6oq
CSS @ property → goo.gle/3yvY2M9
CSS.registerProperty → goo.gle/3zzNEUK
A collection of JavaScript seeded random number generators → goo.gle/3mNoDBX
A real random number generator → goo.gle/3jweBTL
When Chrome changed its Math.random implementation → goo.gle/3Dxd6N4
Final example → goo.gle/3gNAKLK
More videos in the HTTP 203 series → goo.gle/HTTP203
Subscribe to Google Chrome Developers here → goo.gle/Chrome...
Also, if you enjoyed this, you might like the HTTP203 podcast → goo.gle/HTTP20...
#HTTP203 #ChromeDeveloper #WebDev - Наука та технологія
I honestly think these videos are one of the best videos on programming out there. At least for web development! They really dive deep into the material. Hugely appreciated; keep up the good work!
Thank you
@@evacserna2281 very helpful cheers
I think its funny how much I like watching two guys having a blast talking about very specific tech stuff. Keep up the great work!
You've got eight episodes left to plan a celebration for your (2**7)th show, which is really a bigger achievement for a series about programming than 120.
Wait til they hit 203..
Though, I will be upset if video 204 is not a blank video
Apologies to folks who are seeing their comments being deleted. I'm working with both UA-cam folks and the moderation teams to try and stop this, but ugh, progress is slow.
Any idea why they are being deleted?
@@WilcoVerhoef it's two different things really. The moderation service we use are an external company, and they moderate comments without understanding the content of the video, so sometimes we'll make a reference to, say, Surma's t-shirt in the episode, so people will also refer to it in the comments, and those comments get deleted because the moderators see it as a reference to the "presenter's appearance", even though the comments were fine, particularly in context. They also err on the side of "delete" if they're unsure. Both of these things are fine. It's unreasonable to expect them to watch and understand all the content. Also, some of the deleted comments on this channel are genuinely horrible, particularly if the presenter is a woman, so being cautious is the right move. However, what we want is a way to restore deleted comments if we feel they were wrongly deleted (which is supported by UA-cam's API). Unfortunately the external company are refusing to do this, and are insisting that they permanently delete comments.
The other issue is, as far as I can tell, internal. Comments featuring links seems to be auto-deleted by some bot. I guess it's an anti-spam measure, but it's particularly annoying on this channel as we struggle to link to demos, browser issues, specs, MDN etc etc. I've been filing issues about this internally but have struggled to get a response beyond "your call is important to us". The whole thing is pretty frustrating.
Seeded PRNGs are invaluable for networked games, too. A game with no randomness is often boring, but if you use truly random numbers, then the only way to sync data between players is to transfer every randomly generated number over the network, which is super inefficient. With seeded PRNGs, you can just have the host generate a single seed, pass it over the network, and from then on all the data is inherently synced with each machine running its own PRNG.
That may be relatively common knowledge, but I bring it up because with how much JS is being used for game dev these days, I'm *shocked* it still doesn't have a native way to seed its RNG.
Except it is vulnerable to exploitation. Any player can generate the whole future outcome.
Half the time I have absolutely no idea what is being talked about (I'm not a programmer) but the interaction between you guys is a fascinating watch nonetheless. Keep up the good work!
Similar: I code but not much frontend, nothing as deep as this, but I just enjoy these fireside chats. They keep me in touch with the art of the possible on the frontend, especially as I expect all apps will one day be web apps.
Great episode.
Love the creativity and attention to detail, and your witty back and forth makes it even more fun to watch.
Keep it up guys 👍
Loved the fleck -> stain moment! Another great video, this one really got some ideas flowing and made me want to try some things out. Thanks for exploring the outer reaches of web development and sharing them in such an engaging format.
Almost gave this one a miss based on the title but wow am I glad I watched this. As always I learn SO much from these videos. Truly a master class, thank you!
A good candidate for this is 2D perlin noise, it is very frequently used in game dev to generate infinite continuous things like terrain, sky, stars etc. no need for tiling. You don’t get the vertical and horizontal bands with no particles crossing it effect.
For complete perfection, you should calculate the squares to the left and above the image. Your current solution will never have blobs that are centered off the left or top and overflow into the visible area.
Ohhh, I hadn't thought of that, good catch! There's also the issue of blob popping in on the right/bottom that overlap their grid bounds. You could fix that by expanding the grid earlier, based on the maximum diameter of a blob size.
@@jakearchibald Is it possible to cache images? I'm not sure if worklet shares instances or have any other form of sharable state?
@@aliaksei.martsinkevich the browser can do that automatically, but I don't think it can be done manually. The context doesn't have access to the bitmap stuff from canvas.
Finally know what Chrome does with it's RAM usage: Storing these INFINITE BY INFINITE backgrounds...
another great episode as usual, guys! I was a bit surprised you didn't add a random seed to initialize mulberry32 the first time, so we get a different pattern each time the page is refreshed
You can't do that within the worklet itself, since many worklets may be created to parallelise the work. However, --fleck-seed could be set to a random number by JavaScript.
Speaking of crypto and RNGs/PNGs, it turns out that even "true" RNGs can sometimes produce deterministic results if they run out of entropy (for seeding their internal randomization function). That's a security bug that crops up in low powered devices which tend to have fewer sources of entropy. The reason why is because once they run out of entropy (and if they don't throw an error), they could return 0 which, of course, is predictable if you can query it quickly enough. So even TRNGs can still be insecure if not handled properly!
Just tried it out. I tried it out with the Rendering Tab open as well and paint flashing. It's beautiful, worklets are faster than I thought. Should definitely try out all of Houdini's prowess
Thanks for this great video, it really got my brain going today. I paused at 14:55 to think about it on my own. I briefly thought of breaking into boxes but I realized they would still mess up the prng depending on the width, as later revealed in the video. I thought about somehow iterating them diagonally, but it wasn't obvious how to do that and you might have to compute a lot of boxes that aren't needed if the element is very tall or wide. I also worried that boxes would somehow create visible seams, but apparently that wasn't a problem for you. Lastly, having to explicitly specify the block size and blob count seemed icky.
So instead my solution iterates one row of pixels at a time, checking each pixel in that row. Get a random number for that pixel, compare against a "density" css variable, and draw a blob or not. For each row, create a new prng instance based on the original seed plus the current row number. I wondered if my solution would work so I downloaded the code and played around. After dealing with the usual bugs I was happy to see my solution works just fine.
I wonder if my version is slower or not. My version calls next() for every pixel in the space. Your block based version only does work for the exact number of blobs that will get created, but some of those blobs are drawn offscreen, depending on how well the block size tiles the space. Also your version creates more instances of the prng.
Anyway it was a great exercise stimulated by a great video. I definitely plan to watch more from this series. It took me a few minutes to wrap my head around fork(). It's pretty clever. I do wish it were easier to play with the code. Is there any way to get it working with JS fiddle or equivalent? Also I thought I would need to view the page with a local web server since the Houdini usage page says it's required, but I found it worked just find loaded locally as a file url.
paint(ctx, size, props) {
...
const randomX = createRandom(seed);
for (let x = 0; x < width; x++) {
// const randomY = randomX.fork();
const randomY = createRandom(seed + x);
for (let y = 0; y < height; y++) {
// const randomItem = randomY.fork();
const randomItem = randomY;
// Randomly draw a blob at this (x,y)
if (randomItem.next() < density) {
let radius = baseSize;
if (randomItem.next() > 0.125) radius /= 2;
if (randomItem.next() > 0.925) radius *= 4;
radius = Math.max(1, Math.min(radius, 24));
radius *= 0.7;
const color =
colors[Math.floor(randomItem.nextBetween(0, colors.length))];
drawBlob(ctx, randomItem, x, y, radius, color);
}
}
}
}
Create a new random number generator for each pixel, with the pixel coordinate as the seed. Now you can use next() to randomize the properties of that coordinate and not worry about the canvas size. Alternatively use a 2D procedural noise algorithm for more interesting patterns.
An additional optimization would be to procedurally generate which coordinates to populate straight away, rather than iterating over every coordinate and discard the majority. A simple way to do this is to generate the gaps between each populated column in a row, with the row index as seed.
And of course since these seeds are static, you have to also mix them with a random number that is generated for each unique image, or they will all look the same.
This sounds significantly slower than the final result in this video, or am I missing something?
The important bit is that using the coordinate as a seed to generate its properties, since that allows for more controllable results through noise as opposed to pure randomness. How you pick those coordinates really depends on your use case, I'm just spitballing.
Another way to do that is to uniformly distribute them on the canvas according to the desired density, and then use their coordinate (and a seed) to generate a random offset.
@@emilemil1 right, but I cover the coordinate thing in the video. The grid ends up using a coordinate system by being two dimensional, but without the slowness of generating a random number for each pixel.
"each pixel" in this case refers to each coordinate selected in the innermost loop, not every pixel on the canvas.
haha party popper post effect was 👌
Don't mind me, I'd definitely use this effect in my next web project!
Great content you guys.
I'd have explored Perlin noise to generate peaks and color above certain thresholds
I'd be interested in seeing an implementation of this! Feel free to fork the version I made (link is in the description)
Thanks for the demo, and sorry the links keep getting deleted. I guess this kind of thing would be much faster with WebGL since the processing can be per-pixel.
There's a lot of variables and such that are being set every paint. I'm not sure how the get css variables are cached but isn't searching by string expensive? Is there a way to get css variable references? To get/set them quicker? Like in OpenGL for example you'd get shader uniform IDs from strings first, then use those to get/set values. Surma would probably know what I'm talking about, I'm more a game developer.
Totally "stealing" that multidimensional random trick. So much better than what I do right now (not use the full size of the seed and increment) and easier to reason about when using it!
Browser support is still pretty lacking - so probably better off with an SVG for now. That said, SVGs _can_ be generated, and they can be configured to inherit colors from outside.
The Houdini How site has a polyfill for other browsers (see the link in the description)
@@jakearchibald oh nice! Don't know what I'll make but I'm really excited to give this a try. Thanks again for creating these videos!
best duo ever
Great series, thanks.
I sure did learn a lot from this video, but I also couldn't help but think "this could be achieved with about a dozen lines of SVG filters"
I'd like to see an SVG filter version! It'd be interesting to see how it compares in terms of performance
When you talked about using both coordinates as the seed of the PRNG, I though you could, you know, _interleave_ them to get the actual seed. For example, if you had 123 and 89, you'd get 18293. You can do it bit-by-bit, byte-by-byte or - whatever - word-by-word, so you'd just do x*0x10000 + y...
But your solution is cool too 👍
It gets really slow when the screen is zoomed out. I wonder how these things can be optimized
Yeah, I'm curious too. The paths could be combined so it's only one paint command per colour, rather than one paint command per blob. I have no idea if that would be much faster. I guess profiling the current code would be the first step.
Hah I tried a version that did a single fill per colour and it was significantly slower
good stuff
As web developers we’re YEARS away from being able to use CSS paint.
Nice work
I enjoyed this so much 😍
I'm currently lead JavaScript developer at a company
You guys are real professionals 🎆
Hopefully I'll be as good as you one day
Is there any plan to add this worklet to houdini.how?
Ohh that's a good idea. I'll check with George.
And it's there now!
Would it have been possible to always create a grid that is the size of the screen (because you can never display something off of the screen). Then on animate it wouldn't have to recalculate each time. It would just clip a certain size of the grid to use. The tradeoff being the first paint does more work, but animations don't require reruns of the nested loops. Even if it's possible, I'm glad you showed this way!
Elements can be bigger than the screen, eg you scroll to see more
@@jakearchibald I meant "display" as in the human form rather than the HTML layout one. So what I was thinking was something like background-attachment: fixed. But I guess that would be more of a style decision rather than performance.
@@jakearchibald it could always lazily be expanded on demand.
yeah but how'd you hook up that joycon to the browser?
They're bluetooth, and works with the browser gamepad API. It just… works 😀
“Did we start at one??” lol
To be fair, HTTP status codes don't 😀
Final example link seems to be broken for me.
Is anyone else seeing this problem?
**edit** Ignore me, I think it's my WORK's sophos blocking stuff.. Absolutely fine when I turn on a VPN to bypass their security.
"Anyone who attempts to generate random numbers by deterministic means is, of course, living in a state of sin." John von Neumann
Hello,, mantap google🤝🤝🤝🙏🙏🙏🙏
Random Paint Effects in CSS Word Art - SEO link to domain name
dear google Plesse add the pc add on system in mobile chrome 🙏
cool
🏋🏾♂️🏋🏾♂️🏋🏾♂️
im first :)
🙄🤣 keep it up
Just kidding 😄😄
😄