-
Notifications
You must be signed in to change notification settings - Fork 0
Home
Welcome to SalamiVG! This guide should get you going to generate your first sketch.
Install Node.js, or some other JS runtime if you're feeling frisky. Officially, SalamiVG supports Node 16+.
npm i --save @salamivg/core
Create a new file called salamivg-1.js
.
Open the file in your favorite text editor or IDE and add the following code:
import { renderSvg } from '@salamivg/core'
const config = {
loopCount: 1,
openEveryFrame: false
}
renderSvg(config, (svg) => {
// magic coming soon!
})
Now run the file, and inspect the output!
my_first_svg=$(node salamivg-1.js)
cat $my_first_svg
You should see something similar to this printed to your console:
<svg viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100" height="100"></svg>
-
import { renderSvg } from '@salamivg/core'
imports therenderSvg
function from the lib. This function is the most common entrypoint to all sketches. -
const config = { ... }
defines our sketch's configuration. This can include more properties which we'll cover later, but for now it is important to know the following:-
loopCount
controls how many times the sketch runs. Running more than once is probably only necessary if you are either:- iteratively building up some global state which doesn't reset between renders, or
- using some type of random generator which will produce slightly different results between each sketch. In this case, multiple renders can be fun to generate all at once and compare the output.
-
openEveryFrame
controls whether or not SalamiVG attempts to open the SVG it just generated, using your system'sopen
command. This will typically open the SVG in a browser, but can be configured on a per-system basis.
-
-
renderSvg(config, (svg) => { ... }
does all of the following:- builds an SVG using the builder callback
- renders the SVG to a string
- writes the SVG string to a file
By default, the written filename is logged to the console, which allows us to capture it to a shell variable and then use cat
to print it out.
Congratulations, you've written your first SVG with SalamiVG! Now let's do something more interesting.
Let's draw some randomly-placed circles. This is not an exciting result, but it demonstrates several key ideas behind how to use SalamiVG for your own sketches.
This time we'll build the script piecemeal so we can discuss the relevant parts as we go.
import { renderSvg, randomSeed, randomInt, random, createRng } from '@salamivg/core'
We'll need a few base components for our "random circles" sketch:
-
renderSvg
, discussed above -
randomSeed
, a function which generates an integer string which can be used to see an Rng (a pseudo-random number generator (do not use for cryptographic purposes)) -
randomInt
, a function which returns a random integer in the specified range -
random
, a function which returns a random float in the specified range -
createRng
, a function which takes seed value and returns an Rng function. Rngs should return a pseudo-random number in the range [0, 1]
const config = {
width: 100,
height: 100,
scale: 2,
loopCount: 1,
openEveryFrame: true
}
Most of these values are the default values, but to review:
-
width
: the width of our sketch. In SVG terminology, this becomes the width property on theviewBox
attribute -
height
: the height of our sketch. Similar towidth
, this will actually be used in theviewBox
-
scale
: if we would like to render a final SVG that is larger than ourviewBox
, we can do so with thescale
property. In this case, usingscale: 2
will give our SVGwidth="200"
andheight="200"
attributes, while keeping ourviewBox
set to0 0 100 100
-
loopCount
: discussed above. The default value is1
so this can safely be omitted if desired -
openEveryFrame
: discussed above. We're changing this totrue
so you can see the rendered SVG after generating it.
Before we start the render loop, we will define a mutable seed
variable. The value of mutability will be explained later.
let seed = randomSeed()
Next, we'll start our render loop and define some values
let seed = randomSeed()
renderSvg(config, (svg) => {
svg.filenameMetadata = { seed }
const rng = createRng(seed)
const numberOfCircles = randomInt(4, 10, rng)
})
-
filenameMetadata
is an optional object which can add descriptive information to the written filename. I like adding theseed
to the filename, so if a particular render is particularly pleasing, I can use the same seed again to generate the same output. -
createRng
: we create an Rng using the seed value declared outside the loop, and assign to a variable -
randomInt
: we randomly decide how many circles to generate
Finally, we can draw our circles
let seed = randomSeed()
renderSvg(config, (svg) => {
svg.filenameMetadata = { seed }
const rng = createRng(seed)
const numberOfCircles = randomInt(4, 10, rng)
svg.fill = '#333'
svg.stroke = '#ee00aa'
for (let i = 0; i < numberOfCircles; i++) {
svg.circle({
x: random(0, svg.width, rng),
y: random(0, svg.height, rng),
radius: random(10, 20, rng),
})
}
})
If you run this script, you should get an output similar to this:
Let's break down the additional elements:
-
svg.fill = '#333'
: sets the fill to the hex value#333
. This is automatically inherited by any child components (in our example, circles) -
svg.stroke = '#ee00aa'
: sets the stroke to the hext value#ee00aa
. This is automatically inherited by any child components. -
for ...
: create our randomly-specified number of circles -
svg.circle()
: draw a circle at a randomly-generated(x, y)
coordinate with a randomly-generated radius
Now you've drawn elements to your SVG: you're a pro!
Now that we have the basics, we can augment our sketch to add some more flair. Let's define two different color spectra (one for the fill, and one for the stroke) and then generate several variations of our sketch with different values automatically created each time.
Add the following imports to your top-level import statement:
ColorSequence
Update your config to loopCount: 5
. You can do more or less as you prefer.
Define two color spectra anywhere in your script:
const fillSpectrum = ColorSequence.fromHexes([
'#2E3532',
'#7E9181',
'#C7CEDB',
'#A0AAB2',
'#94849B',
])
const strokeSpectrum = ColorSequence.fromHexes([
'#253031',
'#315659',
'#2978A0',
'#BCAB79',
'#C6E0FF',
])
The ColorSpectrum
class creates a smooth gradient between all the colors provided, and allows us to index into the gradient with the at()
method. ColorSpectrum.prototype.at()
accepts a number in the range [0, 1]
Update your renderSvg
callback to randomly select a color on each iteration. Also, add a post-loop callback to update the seed value.
renderSvg(config, (svg) => {
svg.filenameMetadata = { seed }
const rng = createRng(seed)
const numberOfCircles = randomInt(4, 10, rng)
for (let i = 0; i < numberOfCircles; i++) {
svg.circle({
x: random(0, svg.width, rng),
y: random(0, svg.height, rng),
radius: random(10, 20, rng),
fill: fillSpectrum.at(random(0, 1, rng)),
stroke: strokeSpectrum.at(random(0, 1, rng)),
})
}
return () => {
seed = randomSeed()
}
})
Your full sketch should now look something like this:
import { renderSvg, randomSeed, randomInt, random, createRng, ColorSequence } from '@salamivg/core'
const config = {
width: 100,
height: 100,
scale: 2,
loopCount: 5,
openEveryFrame: true
}
let seed = randomSeed()
const fillSpectrum = ColorSequence.fromHexes([
'#2E3532',
'#7E9181',
'#C7CEDB',
'#A0AAB2',
'#94849B',
])
const strokeSpectrum = ColorSequence.fromHexes([
'#253031',
'#315659',
'#2978A0',
'#BCAB79',
'#C6E0FF',
])
renderSvg(config, (svg) => {
svg.filenameMetadata = { seed }
const rng = createRng(seed)
const numberOfCircles = randomInt(4, 10, rng)
for (let i = 0; i < numberOfCircles; i++) {
svg.circle({
x: random(0, svg.width, rng),
y: random(0, svg.height, rng),
radius: random(10, 20, rng),
fill: fillSpectrum.at(random(0, 1, rng)),
stroke: strokeSpectrum.at(random(0, 1, rng)),
})
}
return () => {
seed = randomSeed()
}
})
When you run this sketch, you should get 5 variations of the same sketch, each with a different output. An example output might look something like this
There are a few important notes about our changes
-
fill
andstroke
were set directly on eachcircle
when we drew the circle. When these values are not defined, it will inherit from the parent, but any explicit values will always take precedence. - We returned a "post-loop" callback at the end which set
seed
to a random value after each iteration. This is the beauty of using a mutableseed
value. Inside each render loop, we create a seeded Rng from theseed
value. This gives us predictable random values for the render loop. However, each iteration should have a different seed in order to be visually interesting and unique. This is accomplished by randomizing theseed
after each loop.- If desired, additional side-effects can be added to this post-loop callback
- The primary benefit of encapsulating this in a callback is so we can ensure that any state mutation doesn't impact the script output until after the SVG has been rendered.
Take a look at the guide to learn more about some concepts behind SalamiVG.
Check out some examples in my personal generative art repo to see more ways that SalamiVG can be used for creative coding!
Please
- Open a GitHub Issue if you find a bug, have a question, or have a suggestion for the docs
- Have fun! The only point of creative coding is to enjoy yourself, so try to do that!