 120. Does that mean we've done 100? It doesn't mean we've done 120 episodes. Does that mean 120 episodes? 120 things, including trailers and stuff. But yeah, I think, actually, I think you once, we were close to 100 episodes because we had another little in between. But yeah, it's close. Should have bought a cake. Anyway, just like one party popper. Okay, today I want to talk about random paint effects. Random paint effects. This is paint effects, but random. Yep, absolutely. Nice. This effect is done with a CSS Paintwork Club, newfangled technology. Yeah, I didn't make this. This was done by George Francis. I saw this code pen. Oh, pretty. Lovely. Look at all the colors. And I wanted to recreate this. I wanted to start with, you know, the simplest solution in my head. We're like, oh, I can do better. Well, well, yes, of course, but well, no, actually, because I the reason I'm saying this is the first solution we're going to look at is way worse than what George did. I'm not going to. This is not making fun of someone's code. I'm going to start deliberately a lot worse than what George did. So we can make it more realistic for what you would usually do. Yeah. And so I'll discover some of the problems he had to solve in order to make this work. I would say if you've not seen CSS Paint Worklets before, we have this lovely website, Houdini.how, because you can use them to do different border effects, which is, I think it's actually really good key use of this, because if you've got the box with the arrow coming out of it, like a speech bubble thing, speech bubble or tabs, which have the rounded corners on top, but also the outward rounded corners. Yeah, CSS can do those. Can it? No. Easily, at least. Will you end up just using a bit of SVG or something, but then you're locked into the color, the one color that the SVG is using or what? Anyway, Paint Worklet lets you destroy it with JavaScript. And it's all good. But I want to talk about these random effects. Yeah. Because I think there's a lot of interesting problems to solve here. How do we start doing this? So, we're going to start off with a little bit of CSS. This is newfangled app property. This is where you define a custom property and give it a type. The JavaScript version to declare a custom property with a type has it because it's longer, but now we can do it declaratively. Hurrah. Yes. I do not, I've forgotten that there was a JavaScript version for this. Yeah. It always annoyed me because, you know, you kind of feel like, wait, I'm already declaring my properties or you use your properties in CSS, but you haven't really told the browser what they are. And then later on, JavaScript, like eight seconds later kicks in with like, oh, by the way, it is a number. Exactly. And that's what this is doing, right? It's really just giving it a type, which means it can animate it. And that's really important. Yeah. Because you couldn't animate these properties before. If you had like, you know, if you had red and black, and you had a transitional, it would just snap because it's two strings. It's a string. So here we're saying color plus, which is some colors separated by space. And give it a default value, which is great to say it inherits all of that sort of stuff. I like Fleck, by the way. That's not me. That's George. He picked the name for it. So German for stain. Is it? Yeah. So it should be paint stains. It's a paint stain. I see where he went with Fleck. It is better, isn't it? And now we've got the count as well, which is how many stains we're going to make on our canvas. And then here this is just as using that stuff. So give it a background color, give it some colors. And for the background image, use this fancy paints thing. Paint my stains. And give it a name, which is in this case, Fleck. The rest of our work is going to be in JavaScript. That's the whole point. That's it? Well, it's not. Well, no. But that's the main thread bit. Yeah. The rest of the work is going to happen in this magic worklet thing. And here's what this looks like. So we're going to give it this name, Fleck. That's what we used in the CSS. And give it a class. And the first thing I'm going to say is, like, here's the properties that I care about. Yeah. For this thing. Because this way the browser knows when it actually needs to repaint. If a property changes that you don't care about, no need to repaint. And that's actually quite a nice optimization. Yes. So that means if you change, like, text color, it knows that this background drawing isn't part of that. It can use the cached version. It can use the cached version. It's good. So we care about the count, care about the colors. And now everything else happens in this paint function. So we've got this context object, which is like a 2D canvas. Yeah. Like ish. Pretty close. I think that's some stuff we're saying like text. Yes. And some bitmap stuff. It's a subset of 2D canvas. Yeah. So it's very familiar. And then we've got the bounds there, the width and height. And then the properties, which is the stuff we've asked for. It makes a lot of sense. And there we go. Get the count. Get the colors. By the way, this is type 2M. So because flag count was declared as a number, we will immediately get a number. With colors, we will get an actual color and can even tell. I think you can even say stuff like give me this as an HSV or as a hexadecimal. I think in this case it just gives you as a string, but I'm not 100% sure. It would make sense. Maybe this is a message between type 2M in the spec and type 2M, what has been implemented. Yeah, it could just be something that Chrome hasn't done. But yeah, that dot value comes out as a number. If we didn't do that app property thing before, it would come out as a string because it wouldn't know any better. That's an important part of it. And now we're just going to draw blobs. So for however many, for the number that we want to draw, we're going to draw a blob. And if you wanted to know exactly how I was going to draw that blob, you're going to be disappointed because I'm just going to hide it all behind this function and pretend it doesn't exist. I'll put the code in the description if you want to take a look at that. But I don't think it's the interesting part. I actually reused the blob code from Squoosh. Ah, yeah, you just picked out, hang on that one. Yes, exactly. I've just used circles, but fine. Getting my money's worth. It works out how to draw blobs. So yeah, and now I'm just going to say place that a random X and Y. So random between zero and width, height, whatever. Random size. It's a little bit more complicated than that. I want to say like just no bounds? Just any size? Let's pretend that a size between zero and one is fine, which is what this kicks out. Zero pixel or one pixel. It's a little bit more code, but it's not, it's whatever. It's not the relevant part, the interesting part. Yes. If you want to see, have a look at the code in the description and then just a random color there. Makes sense. Job done. Cool. Oh, here's a random function. That is the standard random function. It'll get you through a Google code interview, maybe, probably. So, I mean, there's some good maps going on there, look. Minus and plus and multiplication. It's like a minus from and a plus from that cancels out. So, you know. Should have done a, yeah, a divide in there just to, just to, you know, use all of the operators. There aren't any more operators are there? It's just those ones. I mean there's an exponentiation. You can do double star. Well, we'll get there later on. Oh, boy. Oh, boy. So, here's the result. Pretty. Yeah. Well, on you. What I'm going to do, I'm going to animate the width of it. Don't animate width. It's a bad thing to do. Well, so I am animating width here, but this same thing could happen just with the contents changing that changes the size of the element. Yeah. And yeah, it's, well, it's random in it. Here's an interesting one. I'm going to scale it. So, you saw it change right at the start. But then not for the rest of the animation. And that is because of layer promotion. So, because it's, yeah, Chrome does it scaling on the compositor thread. As it should. On a, as a texture. And so it had to do a paint right at the start to lift that into its own texture. Does the scale and it just does that as a bitmap scale. So, you're not seeing that random thing. That's also gets slightly blurry. Uh, no. No. Bad surmer. Incorrect. No, it will draw a full resolution. That's the promotion because it, because it's a declarative CSS animation. It will draw a full resolution at the start because it knows. So it's a big resolution scale down rather than a small resolution scaled up. Exactly that. Yeah. Looks a bit blurry. You just need better glasses, mate. That's what's going on here. Uh, yeah. But now we're talking about a Chrome implementation, right? Like the effect we've created is leaking Chrome's implementation. This is going to work different than other browsers. Yeah. Some people are sensitive to flashing images. That stuff is happening out of our control. So we're going to fix that. That's fixed it. And the way we're going to fix it is using seeded random numbers. This has been one of my long, one of the one long sending issues with JavaScript because we have math.random. We do. Great. But you can't, it is a pseudo random number generator. So it, it has a seed and it calculates those random numbers. But you can't control that seed. You will always get new random numbers, which maybe sounds desirable at first. But in many scenarios, you actually want to recreate the same random sequence of numbers, which is exactly what we want to do here. So this is mulberry32. It's a good name. Let's go through how this works. No, I'm not going to go through how this works because I have literally no idea. Hey, it looks smart though. It looks really clever. There's a lot of math there. I think it's the kind of math.imol integer multiplication to make it extra fast. Yes. And that is why I use this. Okay. So this is a random number generator that uses 32 bits of state, whereas the one in Chrome uses 128, 128. The only reason I know that is because whenever I need seeded RNG and JavaScript, I look up this old block, not even that old on blog post on v8.dev, where we had a report that the RNG that v8 uses isn't actually random enough. And people had found a couple of images where they filled in random pixels and you could see patterns. Yes. And so we push a blog post, hey, we have a better RNG now. It's called xorshift128, I think. And in this blog post, yes, just like this one little function, t++, and always copy, paste that one, turn it into JavaScript. And I have seedable RNG. Yep. The reason I picked mulberry32 is it was, it's the smallest and the fastest. And yes, because it's got 32 bits of state rather than 128, it's numbers going to come back round more frequently. But we're still talking about the billions, worst case, I guess. And also what you want to do with this is you want to create blotches on the screen. Yes. Don't ever use either of these, any of these for cryptography, because for cryptography, you don't want pseudo random numbers. You want actual random numbers. Yes, which is why we've got crypto.subtle or crypto get random values. I can't remember it off the top of my head. It's in crypto somewhere. It's in crypto somewhere. We do have an actual random number generator. All right. So we're going to use this instead of math.random. Because it means like, I can give it a seed called next three times, get three random numbers, and later random to me, it looks, it's definitely like, even just from those three, we know it's going to be random. Is this random? Yeah, look at it, big, long numbers and one starts with seven, one starts with a one. Wow. It's the same ones every time with the same seed, but if you change the seed, you get different random numbers, which is exactly what we were talking about. So I'm going to add another property. So people using this can provide their own seed, so they can have different patterns, different bits on the page. You could animate this if you really wanted to, because you would get that random effect back of it drawing every frame, whatever. Here's how I'm going to integrate that. So the seed is one of the input properties there. And down here, get that number, pass it to mulberry32. There's our new random number generator. Oh, and here instead of random, it's random.next. There we go. Here it is. Yeah. Moment of truth. There you go. That was different this time. It's not random anymore. It's not changing every frame. Yeah. Very good. I kind of expected the blobs to grow. But I guess from the code, it's actually the blob size was independent of the canvas size, while the position is not. So like, again, it's exactly happening as you wrote it down, but it just, yeah. Yeah. At first it sort of looks like the canvas is stretching, but those blobs are all staying exactly the same size and shape. Yeah. It's the centers that are just being stretched. Exactly. And that does have a bad side effect, I think, is that this looks very sparse, and this is quite dense. And that can probably be quite undesirable, if I think about it, because you might use it on a very small element as a background image in the corner for a sidebar, but also as a big background image for your header or whatever, but you would want them to look consistent. Exactly. And it's going to look not consistent. Inconsistent. That's the word we have for that. Word professionals. So we're going to fix that. What we really want is instead of a count, like a static number for every box, we're going to make it a density. So that's not an integer. It's going to be a number, which means we can use decimals. And this is going to be the chance of each pixel being a blob. Or some, yeah. Or something like that, whatever. That's what I went for. So yeah, not count, it's density. And get that number. And our count is now just going to be width times heights times density, job done. That makes a lot of sense. Here it is. And there we go. And this is quite a nice effect, I think. I feel like for cases where you don't animate, this is good. If you just use them once, cool. But obviously, sometimes these things happen, someone resizes their window or portrait to landscape, and then you have... It looks a bit odd. I guess, yeah, it could be annoying. I think what would be classic CSS behavior would be it just reveals more blobs. Yeah, you kind of want to actually have just a really large image and just use a crop and then you make it bigger, which is obviously hugely wasteful. But that would be the nicest effect around this. So let's achieve that effect, but without the waste. The way I would do that is by splitting this up into a grid, and we are going to deal with each cell independently. So we're going to pick a number of blobs to put in each cell, deal with that, and then move on to the next cell. And we only create as many cells as we need. Right. And so you have maybe a tiny bit of wastage, which you can see around the corner, I guess, because not every candle size will be perfectly tileable with your tile size. But I mean, that's the very definition of trade-off. Yeah. I used 300 by 300 for these cells. If you use one that's too small, then it's going to start looking too uniform, because it's going to be placing 50 blobs for each tiny little square. It's going to look too uniform. If you make it too big, you get more of this waste. Yeah. So whatever, 300. I didn't do any science to pick that number. I just went 300, and it looked fine. So I stuck with 300. So let's implement that. We're not using density anymore. It's now going to be per cell, count per cell, whatever. Going back to account. And so cell size 300, we could make that configurable as well. Nice hard coded. Yeah. Whatever. And then, yep, per cell, get that from the type to M. And so instead of iterating over just the number of blobs we're going to draw, we're going to iterate over the columns. So x, if you could start with y, it really doesn't matter. Then we're going to do y. Now we're dealing with the cell level. And then however many we're going to draw per cell, we're going to draw one of these blobs. So instead of these things being between zero and height and zero and width, it's going to be somewhere within that cell. That makes a lot of sense. Job done. Here it is. And da, da, da, da, da, da, da. Pretty. Ta-da. Job, job solved. Done. End of episode. I was just thinking, because when you had the numbers, you were iterating over them in column major as it's called. Is that what it's called? Okay. Take your word for it. So what happens when you animate height? Yeah. It's all gone wrong. That works on the x-axis. It's a 50% improvement. It's fine. Just don't animate height, you know? And genuinely, I didn't see this coming the way you did. Here's what's happening. So we've got our different cells. And keep an eye on cell number two, we'll say. And as we reset it down, it moves. And this is the problem here is our cells are moving around. Yeah. Number two, specifically there with that dark blob at the bottom, just left of center. You'll see as we, it's to the right of number one, and then it becomes below number one. And it's the same cell because we can see those blobs are in the same place. Yeah. We've created a problem. We don't want those cells to move around. So the solution, use coordinates. Oh yeah. So my, you've basically, you, you, you turn those coordinates into a seed for a new number random generator for each tile. Pretty much. Yeah. Pretty much. So the benefit of this is, yeah, no matter how wide or tall it gets, like these cells aren't going to move around because they've got both their exposition and their positions, like it's fully coded into it. The way I achieved this, and you might have done it a different way, but I created multi-dimensional random numbers. Oh, that's a mouthful. That sound clever, doesn't it? It's not actually that clever, but it worked. And it worked because I actually in the end needed more dimensions than just X and Y, because we've got like a count that the number of blobs is a kind of, it's like a third dimension to these numbers. Right. Because maybe in a scenario, you might want to increase the count, but that doesn't mean that you want all your blobs to reposition. You just want new ones to be added on top of what you already have. So it makes sense to model it as an additional dimension. Yes, exactly. And that's what I did. I did this fork function to our mulberry thing. Oh, that's very elegant. I like it better than my version. It's, well, and here's our exponential. So this is just creating a new mulberry 32. And it's using its own random number generator to create a new seed between zero and 32. Right. So that means just like the number sequence of the fork fully depends on the current state of the current RNG. And so it's completely reproducible despite having multiple dimensions. I guess it's the right word, even though it sounds very pretentious. It does sound pretentious. It does make me feel smart though. So I'm going to stick with it. I did original solution to this was just incrementing the seed by one and you're wrapping it at 32 bits. But that became observable in the CSS when you were increasing the seed, you could see that it's like, oh, I just handed hands it down to the next dimension. Then doesn't it? Yeah, you can see that stuff shifting around. It's like, oh, that box just moved over there when I increased the seed where you would have expected it to be completely different. Yeah. Yeah. Anyway, so that's what I did. So now, all the only thing we have to change is down here, we've got our random number generator at the top random number generators, so to speak, the root number. Yeah, because we still want to be taking that seed into account. You change the seed, we want everything to change. We fork it for the columns, we fork it for the cell. And now we have a random number generator that is scoped to that cell. And anything that happens to that is not going to be affecting the stuff that happens in other cells. And yeah, so now instead of random, we're going to be using cell random. And that's it. Dude, the animation. I want to see it. Oh my God. We have seen that one. Hey, that's nice. And I really like its effect because it gives this impression that it's an infinite by infinite canvas, right? That you can just infinite by infinite, we're storing it all in memory. Yeah. Yeah. But it's optimized. So when it's small, it's, it's, you know, just placing a few random blobs. But when it's big, it's having to, you know, that work is scaling up, which is exactly, exactly what you want. It gives the illusion that that stuff is all there all the time. And it is like, you make it small, you reveal it again, those dots are in the same place because it's using the, you know, the seeded random number generator. So yeah, for this kind of like generative art, you really that you want to have random in there, but you want your random to be deterministic because you want to be able to reproduce it. So it's counterintuitive as it may seem. Exactly. Make it deterministic. With CSS, your random needs to be not random. Like it just needs to look random. And I see a lot of, it's obviously early days for CSS paint worklets. But a lot of the random effects have this problem where you change the size of it and it completely redraws. George Francis is the Blobs thing. He actually used mulberry 32. He didn't have this problem, but a lot of them do. So if you want to fix that, this is these are the sort of techniques you use. All the code will be in the description. Go and have a play. What did we say last time? So, so