diff --git a/docs/rkgk.dj b/docs/rkgk.dj index 288dd6f..2b7e634 100644 --- a/docs/rkgk.dj +++ b/docs/rkgk.dj @@ -86,8 +86,8 @@ If you want to draw multiple scribbles, you can wrap them into a list, which we -- Draw two colorful dots instead of one! withDotter \d -> [ - stroke 8 #F00 (vec ((vecX (d To)) + 4) (vecY (d To))) - stroke 8 #00F (vec ((vecX (d To)) - 4) (vecY (d To))) + stroke 8 #F00 (d To + vec 4 0) + stroke 8 #00F (d To + vec (-4) 0) ] ``` @@ -108,26 +108,23 @@ It'll draw the first inner list, which contains two scribbles, and then it'll dr withDotter \d -> [ [ - stroke 8 #F00 (vec ((vecX (d To)) + 4) (vecY (d To))) - stroke 8 #00F (vec ((vecX (d To)) - 4) (vecY (d To))) + stroke 8 #F00 (d To + vec 4 0) + stroke 8 #00F (d To + vec (-4) 0) ] [ - stroke 8 #FF0 (vec (vecX (d To)) ((vecY (d To)) + 4)) - stroke 8 #0FF (vec (vecX (d To)) ((vecY (d To)) - 4)) + stroke 8 #FF0 (d To + vec 0 4) + stroke 8 #0FF (d To + vec 0 (-4)) ] ] ``` ::: aside -I know this example is kind of horrendous to read right now. +Another weird thing: when negating a number, you have to put it in parentheses. -This is because haku currently does not have vector math, which means we have to disassemble and reassemble vectors manually. - -{% Another weird thing: when negating a number, you have to put it in parentheses. %} -{% This is because haku does not see your spaces---`vec -4`, `vec - 4`, and `vec-4` all mean the same thing! %} -{% In this case, it will always choose the 2nd interpretation---vec minus four. %} -{% So to make it interpret our minus four as, well, _minus four_, we need to enclose it in parentheses. %} +This is because haku does not see your spaces---`vec -4`, `vec - 4`, and `vec-4` all mean the same thing! +In this case, it will always choose the 2nd interpretation---vec minus four. +So to make it interpret our minus four as, well, _minus four_, we need to enclose it in parentheses. ::: @@ -163,7 +160,7 @@ If you reorder or remove any one of them, your brush isn't going to work! - Then, `\d ->` lets us name the data we get back from the UI. `d` ends up containing a few useful properties, but the most useful one for us is `To`, which contains the current mouse position (where *`To`* draw). - - We'll get to what all these sigils mean to haku later! + - We'll get to what all these `\` and `->` sigils mean to haku later! - On the next line we have a `stroke`. `stroke` is a _function_---a recipe for producing data!\ @@ -200,11 +197,18 @@ Likewise, negative X coordinates go leftwards, and negative Y coordinates go upw --- -Going back to the example though, `vec` is yet another function, except instead of producing strokes, it produces vectors! +Vectors in haku are obtained with another function---`vec`---though we don't use it in the basic example, because `d To` already is a vector. +Vectors support all the usual math operators though, so if we wanted to, we could, for example, add a vector to `d To`, thus moving the position of the dot relative to the mouse cursor: -Note how it's parenthesized though---recall that function arguments are separated with spaces, so if we didn't parenthesize the `vec`, we'd end up passing `vec`, `0`, and `0` back to `stroke`---which is far from what we want! +```haku +withDotter \d -> + stroke 8 #000 (d To + vec 10 0) -- moved 10 pixels rightwards +``` -And with all that, we let haku mix all the ingredients together, and get a black dot under the cursor. +Also note how the `d To` expression is parenthesized. +This is because otherwise, its individual parts would be interpreted as separate arguments to `stroke`, which is not what we want! + +Anyways, with all that, we let haku mix all the ingredients together, and get a black dot under the cursor. ```haku withDotter \d -> @@ -233,21 +237,23 @@ withDotter \d -> We replace the singular position `d To` with a `line`. `line` expects two arguments, which are vectors defining the line's start and end points. For the starting position we use a _different_ property of `d`, which is `From`---this is the _previous_ value of `To`, which allows us to draw a continuous line. -```haku -[ - stroke 8 #F00 (circle (-16) 0 16) - stroke 8 #00F (rect 0 (-16) 32 32) -] -``` - ::: aside -In haku, by adding thickness to a point, it becomes a square. -In theory it could also become a circle... -But let's not go down that rabbit hole. +In haku, by adding thickness to a point, it becomes a circle. +In theory it could also become a square... but if line caps are rounded, they connect together much more nicely! ::: +haku also supports other kinds of shapes: circles and rectangles. + +```haku +withDotter \d -> + [ + stroke 8 #F00 (circle (d To + vec (-16) 0) 16) + stroke 8 #00F (rect (d To + vec 0 (-16)) 32 32) + ] +``` + - `circle`s are made up of an X position, Y position, and radius. - `rect`s are made up of the (X and Y) position of their top-left corner, and a size (width and height).\ @@ -261,21 +267,23 @@ But if describing data was all we ever wanted, we could've just used any ol' dra Remember that example from before? ```haku -[ - stroke 8 #F00 (vec 4 0) - stroke 8 #00F (vec (-4) 0) -] +withDotter \d -> + [ + stroke 8 #F00 (d To + vec 4 0) + stroke 8 #00F (d To + vec (-4) 0) + ] ``` It has quite a bit of repetition in it. If we wanted to change the size of the points, we'd need to first update the stroke thickness... ```haku -[ - stroke 4 #F00 (vec 4 0) - stroke 4 #00F (vec (-4) 0) - --- -] +withDotter \d -> + [ + stroke 4 #F00 (d To + vec 4 0) + stroke 4 #00F (d To + vec (-4) 0) + --- + ] ``` ...twice of course, because we have two scribbles. @@ -284,10 +292,10 @@ So we also have to update their positions. ```haku [ - stroke 4 #F00 (vec 2 0) - --- - stroke 4 #00F (vec (-2) 0) - -- + stroke 4 #F00 (d To + vec 2 0) + --- + stroke 4 #00F (d To + vec (-2) 0) + -- ] ``` @@ -311,11 +319,12 @@ So we can define `thickness` to be `4`, and then use it in our scribbles. ```haku thickness = 4 -[ - stroke thickness #F00 (vec 2 0) - stroke thickness #00F (vec (-2) 0) - --------- -] +withDotter \d -> + [ + stroke thickness #F00 (d To + vec 2 0) + stroke thickness #00F (d To + vec (-2) 0) + --------- + ] ``` `name = data` is a special operator in haku that tells the language "whenever we say `name`, we mean `data`." @@ -327,17 +336,27 @@ To keep a consistent program structure, haku also forces all your defs to appear You can think of the defs as a list of ingredients for the final scribble. Reading the ingredients can give you context as to what you're gonna be cooking, so it's useful to have them first! +If you find this hard to grasp, just know that _defs usually go before `withDotter`_. + +::: aside + +We cannot refer to `d` from defs, because `d` is only visible on the `withDotter` line and below. +We'll get to why soon! + +::: + Anyways, we can likewise replace our `2` constants with a def: ```haku thickness = 4 xOffset = 2 -[ - stroke thickness #F00 (vec xOffset 0) - stroke thickness #00F (vec (-xOffset) 0) - --------- -] +withDotter \d -> + [ + stroke thickness #F00 (d To + vec xOffset 0) + stroke thickness #00F (d To + vec (-xOffset) 0) + --------- + ] ``` Note how in haku, names may not contain spaces. @@ -347,9 +366,13 @@ This naming convention is known as `camelCase`, and is used everywhere throughou ::: aside Of note is that haku names also cannot start with an uppercase letter. -It's reserved syntax for the future. +Uppercase names are special values we call _tags_. -Right now the only names that start with an uppercase letter are the two booleans, `True` and `False`. +Tags are values which represent names. +For example, the `To` in `d To` is a tag. +It represents the name of the piece of data we're extracting from `d`. + +There are also two special tags, `True` and `False`, which represent [Boolean](https://en.wikipedia.org/wiki/Boolean_algebra) truth and falsehood. ::: @@ -361,10 +384,11 @@ thickness = 8 --- xOffset = 2 -[ - stroke thickness #F00 (vec xOffset 0) - stroke thickness #00F (vec (-xOffset) 0) -] +withDotter \d -> + [ + stroke thickness #F00 (d To + vec xOffset 0) + stroke thickness #00F (d To + vec (-xOffset) 0) + ] ``` So we'll make our `xOffset` calculated dynamically from the `thickness`, to not have to update it every time. @@ -374,10 +398,11 @@ thickness = 8 xOffset = thickness / 2 ------------- -[ - stroke thickness #F00 (vec xOffset 0) - stroke thickness #00F (vec (-xOffset) 0) -] +withDotter \d -> + [ + stroke thickness #F00 (d To + vec xOffset 0) + stroke thickness #00F (d To + vec (-xOffset) 0) + ] ``` Try playing with the `thickness` now! @@ -389,14 +414,16 @@ So far we've only been dealing with strokes. So why not switch it up a little and _fill in_ a shape? ```haku -fill #000 (circle 0 0 16) +withDotter \d -> + fill #000 (circle (d To) 16) ``` How about... some transparency? Recall that colors can have an alpha component, so let's try using that! ```haku -fill #0001 (circle 0 0 16) +withDotter \d -> + fill #0001 (circle (d To) 16) ``` If you play around with this brush, you'll notice how the circles blend together really nicely. @@ -405,21 +432,23 @@ That's the power of Alpha! Now let's see what happens if we draw two such circles on top of each other---one bigger, one smaller. ```haku -[ - fill #0001 (circle 0 0 16) - fill #0001 (circle 0 0 32) -] +withDotter \d -> + [ + fill #0001 (circle (d To) 16) + fill #0001 (circle (d To) 32) + ] ``` How about four? ```haku -[ - fill #0001 (circle 0 0 8) - fill #0001 (circle 0 0 16) - fill #0001 (circle 0 0 24) - fill #0001 (circle 0 0 32) -] +withDotter \d -> + [ + fill #0001 (circle (d To) 8) + fill #0001 (circle (d To) 16) + fill #0001 (circle (d To) 24) + fill #0001 (circle (d To) 32) + ] ``` Okay, this is starting to look interesting, but it's also getting super unwieldy code-wise! @@ -455,15 +484,16 @@ That'll need fixing! Either way, let's define a function that'll make us those circles! ```haku -splat = \radius -> - fill #0001 (circle 0 0 radius) +splat = \d, radius -> + fill #0001 (circle (d To) radius) -[ - splat 8 - splat 16 - splat 24 - splat 32 -] +withDotter \d -> + [ + splat d 8 + splat d 16 + splat d 24 + splat d 32 + ] ``` That's a lot nicer, isn't it---a template for our circles is neatly defined in a single place, and all we do is reuse it, each time with a different `radius`. @@ -481,6 +511,10 @@ To dismantle that weird `\` syntax... A function can have an arbitrary number of parameters listed, separated by commas, and that many parameters _must_ be passed to it. Otherwise your brush will fail with an error! + - Note how also pass `d` into `splat`. + If you're a keen-eyed observer, you will have already noticed that the `\d ->` after `withDotter` is a function, too! + And the values a function uses, such as `d To`, have to come from somewhere. + - And lastly, after an arrow `->`, we have the function's result. Note that a function can only have _one_ result, just like a brush can only have one scribble. @@ -500,19 +534,20 @@ haku limits the use of overloading to system functions for simplicity---adding o Since these transparent circles are so much easier to draw now, let's make a few more of them! ```haku -splat = \radius -> - fill #0001 (circle 0 0 radius) +splat = \d, radius -> + fill #0001 (circle (d To) radius) -[ - splat 8 - splat 16 - splat 24 - splat 32 - splat 40 - splat 48 - splat 56 - splat 64 -] +withDotter \d -> + [ + splat d 8 + splat d 16 + splat d 24 + splat d 32 + splat d 40 + splat d 48 + splat d 56 + splat d 64 + ] ``` Okay, I'll admit this is getting kind of dumb. @@ -545,16 +580,17 @@ Until some threshold is reached, in which case we just make a single circle. The first part is easy to do: haku allows us to define a function that calls itself without making any fuss. ```haku -splat = \radius -> - fill #0001 (circle 0 0 radius) +splat = \d, radius -> + fill #0001 (circle (d To) radius) -airbrush = \size -> +airbrush = \d, size -> [ - splat size - airbrush (size - 8) + splat d size + airbrush d (size - 8) ] -airbrush 64 -- sounds like some Nintendo 64 game about graffiti, lol. +withDotter \d -> + airbrush d 64 ``` But... @@ -588,34 +624,37 @@ color = else #F00 -fill color (circle 0 0 radius) +withDotter \d -> + fill color (circle (d To) radius) ``` - `<` is a function that produces `true` if the second argument is a smaller number than the first argument. - Truth and falsehood are data too, and are represented with the values `true` and `false`. + Truth and falsehood are data too, and are represented with the values `True` and `False`. -- We need three arguments to execute an `if`: the condition, the data to use when the condition is `true`, and the data to use when the condition is `false`. +- We need three arguments to execute an `if`: the condition, the data to use when the condition is `True`, and the data to use when the condition is `False`. What's magical about an `if` is that _only one branch is executed_. + In a function call, all arguments will always be calculated. An `if` only calculates the argument it needs to produce the result. This allows us to use it to prevent unbounded recursion in our `airbrush` example. ```haku -splat = \radius -> - fill #0001 (circle 0 0 radius) +splat = \d, radius -> + fill #0001 (circle (d To) radius) -airbrush = \size -> +airbrush = \d, size -> if (size > 0) [ - splat size - airbrush (size - 8) + splat d size + airbrush d (size - 8) ] else [] -airbrush 64 +withDotter \d -> + airbrush d 64 ``` Neat! @@ -628,39 +667,41 @@ But the airbrush still looks super primitive. Let's try increasing the fidelity by doing smaller steps! ```haku -splat = \radius -> - fill #0001 (circle 0 0 radius) +splat = \d, radius -> + fill #0001 (circle (d To) radius) -airbrush = \size -> +airbrush = \d, size -> if (size > 0) [ - splat size - airbrush (size - 1) - --- + splat d size + airbrush d (size - 1) + --- ] else [] -airbrush 64 +withDotter \d -> + airbrush d 64 ``` Well... sure, that's just a black blob with a slight gradient on the outer edge, so let's decrease the opacity. ```haku -splat = \radius -> - fill #00000004 (circle 0 0 radius) +splat = \d, radius -> + fill #00000004 (circle (d To) radius) --------- -airbrush = \size -> +airbrush = \d, size -> if (size > 0) [ - splat size - airbrush (size - 1) + splat d size + airbrush d (size - 1) ] else [] -airbrush 64 +withDotter \d -> + airbrush d 64 ``` Looks good as a single dot, but if you try drawing with it... it's gray?? @@ -698,8 +739,9 @@ Most commonly, colors are blended using _linear interpolation_---which is essent Mathematically, linear interpolation is defined using this formula: -``` -lerp(a, b, t) = a + (b - a) * t +```haku +lerp = \a, b, t -> + a + (b - a) * t ``` What we're doing when blending colors, is mixing between a _source_ color (the wall), and a _destination_ color (the brush) on each channel. @@ -710,8 +752,8 @@ But due to this reduced precision on the wall, we have to convert from a real nu Consider that we're drawing circles of opacity 0.01 every single time. Now let's look what happens when we try to blend each circle on top of a single pixel... -``` -lerp(0, 255, 0.01) = 0 + (255 - 0) * 0.01 = 255 * 0.01 = 2.55 +```haku +lerp 0 255 0.01 = 0 + (255 - 0) * 0.01 = 255 * 0.01 = 2.55 ``` That's one circle. @@ -721,39 +763,39 @@ But remember that we have to convert that down to an integer between 0 to 255--- This is known as _truncation_. It is not the same as rounding! -For negative results, it gives different results: `floor(-1.5)` would be `-2`, while `trunc(-1.5)` is `-1`. +For negative results, it gives different results: `floor (-1.5)` would be `-2`, while `trunc (-1.5)` is `-1`. ::: So for the next step, we'll be interpolating from `2`, and not `2.55`... -``` -lerp(2, 255, 0.01) = 4.53 -lerp(4, 255, 0.01) = 6.51 -lerp(6, 255, 0.01) = 8.49 -lerp(8, 255, 0.01) = 10.47 +```haku +lerp 2 255 0.01 = 4.53 +lerp 4 255 0.01 = 6.51 +lerp 6 255 0.01 = 8.49 +lerp 8 255 0.01 = 10.47 ... ``` I think you can see the pattern here. This continues until around 52, where the decimal point finally goes below zero, and now we're incrementing by one instead. -``` +```haku ... -lerp(52, 255, 0.01) = 54.03 -lerp(54, 255, 0.01) = 56.01 -lerp(56, 255, 0.01) = 57.99 -- !! -lerp(57, 255, 0.01) = 58.98 +lerp 52 255 0.01 = 54.03 +lerp 54 255 0.01 = 56.01 +lerp 56 255 0.01 = 57.99 -- !! +lerp 57 255 0.01 = 58.98 ... ``` ...and at one point, we get to this: -``` -lerp(153, 255, 0.01) = 154.02 -lerp(154, 255, 0.01) = 155.01 -lerp(155, 255, 0.01) = 156 -lerp(156, 255, 0.01) = 156.99 -- !! +```haku +lerp 153 255 0.01 = 154.02 +lerp 154 255 0.01 = 155.01 +lerp 155 255 0.01 = 156 +lerp 156 255 0.01 = 156.99 -- !! ``` Truncating 156.99 will get us to 156 again, which means we're stuck! @@ -762,7 +804,7 @@ This precision limitation is quite unfortunate, but I don't have a solution for Maybe one day. For now you'll have to construct your brushes with this in mind. -## And more limits +### And more limits There are more limits on top of this, which stem from haku's design. Since it's running _your_ code on _my_ server, it has some arbitrary limits set to prevent it from causing much harm. @@ -771,14 +813,108 @@ haku code cannot be too long, and it cannot execute too long. It cannot consume too much memory---you cannot have too many definitions, or too many temporary values at once. There are also memory usage limits on "heavyweight" data, such as functions or lists. -Basically, don't DoS me with it ^^' -I'm not specifying the precise limits here, because the app will show these to you in the future. -There's no point in documenting them if you can't inspect your brush's resource usage easily. +## Reticles + +Having basic knowledge of functions and scribbles, you may be wondering: _what does that `withDotter` function do?_ +It surely looks a bit magical, conjuring that `d` parameter from nowhere; and `d` contains everything we need to draw! + +Put simply, `withDotter` is what rakugaki calls a *reticle*. +Reticles are pieces of data representing _interactions with the wall_. + +A reticle is usually composed of two parts: the reticle data, defining how the interaction is meant to be initiated, and a _continuation function_. +When your brush gives rakugaki a reticle, it will take the reticle data, and let the user perform an interaction. +Once the interaction is performed, it will give whatever user input it has gathered as an argument to the continuation function, which can return a scribble to draw on the wall, or another reticle. + +rakugaki will continue performing interactions until the brush gives back a scribble. + +`withDotter` is the simplest, most direct reticle for interacting with the wall. +It allows you to paint freely, and gives the brush information on your current mouse position, previous mouse position, and _number of steps_ performed thus far. + +You've already seen the former two properties---those are `d To` and `d From`. +The number of steps is available as `d Num`. + +This is an integer starting at 0, incremented by 1 with each execution of the brush, until you release your mouse cursor. +This allows you to _animate_ your brushes over time! +For example, this brush draws a rainbow. + +```haku +colorCurve = \n -> + abs (cos n) + +pi = 3.14159265 +l = 0.1 -- wavelength + +withDotter \d -> + let r = colorCurve (d Num * l) + let g = colorCurve (d Num * l + pi/3) + let b = colorCurve (d Num * l + 2*pi/3) + let color = rgba r g b 1 + stroke 8 color (line (d From) (d To)) +``` + +Currently, `withDotter` is the only reticle available in rakugaki, and it cannot be chained due to its immediate nature: +`withDotter` continues executing immediately after you move your mouse by the _tiniest_ bit, so it's unclear how to even continue after that! + +In the future rakugaki might get reticles that let you select lines, rectangles, ellipses, curves... but today is not that day. + + +### What's that, `let`? + +I mentioned before that you cannot have defs inside functions. +What you _can_ have though, is `let`s, which define _variables_. + +Unlike defs, which are constant and cannot vary, variables' values can depend on function parameters---and a function can be called with a different set of parameters each time, thus making them variable! + +A `let` always takes the following form. + +```haku +let name = value +then +``` + +It's very similar to a def, with one major difference. +Because a `let` by itself only _names a value_ and does not have a result, it must be followed by another expression on the following line---and that expression determines the result. +The magic is that this continuing expression can refer to the `name` we had previously assigned in the `let` expression. + +::: aside + +Here's a bit of trivia: the variable defined by a `let` is exactly the same as a function parameter. +The `let` above is equivalent to applying the argument `value` to a function taking in the parameter `name`, and returning `then` as the result. + +```haku +(\name -> then) value +``` + +This basic little trick with immediately applying a function stands the basis of a formal mathematical system called [_lambda calculus_](https://en.wikipedia.org/wiki/Lambda_calculus). +It underpins a large number of functional programming languages, including haku, and more famously [Haskell](https://www.haskell.org/)! + +That's right. haku is a cute little Haskell for artists. + +::: + +`let`s aren't only useful for reusability---they're also helpful for breaking your brushes into smaller, more digestible pieces! +Compare the above version of the rainbow brush to this version, where all the `let`s are written inline: + +```haku +colorCurve = \n -> + abs (cos n) + +pi = 3.14159265 +l = 0.1 -- wavelength + +withDotter \d -> + stroke 8 (rgba (colorCurve (d Num * l)) (colorCurve (d Num * l + pi/3)) (colorCurve (d Num * l + 2*pi/3)) 1) (line (d From) (d To)) +``` + +That's one hard to read beast of a `stroke`! + +Generally, if a line is so long it wraps around rakugaki's narrow little text editor, it's probably a good idea to split it into variables. + ## Have fun -With that said, I hope you can have fun with rakugaki despite its flaws. +With that said, I hope you can have fun with rakugaki despite it being in its infancy! You may want to check out the [system library reference](/docs/system.html) now, to know what else you can do with the language---this little introduction barely even scratched the surface of what's possible! diff --git a/docs/system.dj b/docs/system.dj index e3587d0..74bb2a3 100644 --- a/docs/system.dj +++ b/docs/system.dj @@ -63,45 +63,96 @@ Additionally, the syntax `a | b` may be used to signify that one of the listed t - a : number -> number + +- + a : vector + -> vector ``` `-`, when used in its unary form `-x`, returns the number `x` with the opposite sign. +When used on vectors, returns the same vector facing the reverse direction (the individual components are negated.) + +This operation is not defined for colors, because it doesn't make sense to have a color with negative RGBA values. ```haku + a : number b : number -> number + ++ + a : vector + b : vector + -> vector + ++ + a : rgba + b : rgba + -> rgba ``` -`+` adds two numbers together. +`+` adds two numbers, vectors, or colors together. ```haku - a : number b : number -> number + +- + a : vector + b : vector + -> vector + +- + a : rgba + b : rgba + -> rgba ``` -`-`, when used in its binary form `x - y`, subtracts two numbers from one another. +`-`, when used in its binary form `x - y`, subtracts two numbers, vectors, or colors from one another. ```haku * a : number b : number -> number + +* + a : vector + b : vector + -> vector + +* + a : rgba + b : rgba + -> rgba ``` `*` multiplies two numbers together. +When used on vectors, it scales them component-wise. +Likewise for colors. ```haku / a : number b : number -> number + +/ + a : vector + b : vector + -> vector + +/ + a : rgba + b : rgba + -> rgba ``` `/` divides a number by another number. +When used on vectors, it divides them component-wise. +Likewise for colors. ```haku floor @@ -324,7 +375,7 @@ The following functions are used to compare values and work with `boolean`s. -> boolean ``` -If `b` is `()` or `False`, `not` returns `true`. +If `b` is `()` or `False`, `not` returns `True`. Otherwise it returns `False`. ```haku @@ -459,25 +510,6 @@ vecW --- -Note that mathematical operations are currently not defined for vectors. -You may define your own vector operations like so: - -```haku --- Vector addition -addv = \a, b -> - vec (vecX a + vecX b) (vecY a + vecY b) (vecZ a + vecZ b) (vecW a + vecW b) - --- Likewise for subtraction, multiplication, and division. -``` - -Note that haku-defined vector operations like these are more costly the more components they operate on. -Therefore, it's recommended to only define them for two dimensions, unless you really need more. - -```haku -addv2 = \a, b -> - vec (vecX a + vecX b) (vecY a + vecY b) -``` - ## Colors ```haku @@ -520,6 +552,7 @@ For example, consider multiplicatively blending two colors. ```haku -- This is how you can multiply two colors together. +-- Note that the `*` operator works for colors, so you don't need to define this in your brushes. mulRgba = \a, b -> rgba (rgbaR a * rgbaR b) (rgbaG a * rgbaG b) (rgbaB a * rgbaB b) (rgbaA a * rgbaA b) ``` @@ -539,12 +572,12 @@ mulRgba = \a, b -> Note that haku does not clamp colors to the `0` to `1` range. It is perfectly valid to have a color that is out of range or even `NaN`, but when drawing scribbles: +- any number less than `0` is clamped to `0`. +- any number greater than `1` is clamped to `1`. - `∞` is clamped back to `1`. - `-∞` is clamped back to `0`. - any scribble with a `NaN` color is ignored. -Note that just like vectors, arithmetic operations on colors are currently not defined. - Before scribbles are drawn to the wall, colors are converted to 8-bit integers for more efficient rasterization and storage. This means some loss of precision will happen, which may cause issues with brushes like this one: @@ -625,7 +658,7 @@ stroke Creates a stroke scribble, which outlines the provided shape with a stroke of the given thickness and color. -Point shapes are drawn as squares, and `line` shapes have square caps at the line's endpoints. +Point shapes are drawn as circles, and `line` shapes have round caps at the line's endpoints. ```haku fill