Thoughts on a little language for generative art
I’ve been thinking about resurrecting the Marks idea, partly because I’d like to use it for my art and partly because I want to have my 12in23 project (for at least some of the languages) be a simplified Marks parser. (And a little bit because my dad’s name was Mark.)
As a refresher: Marks is going to be a DSL for generative art. In its newest incarnation, it could also be used more generally for composition, but generative art is the focus.
The current plan is to have a number of types of top-level blocks: layers, brushes, path profiles, color definitions, functions, and properties, and probably a few others I haven’t fully thought through yet. A rough draft, which still needs a lot of refining, and which doesn’t yet include a lot of the ideas I’ll include in the notes below:
size 7500x7500
background white
color black #000
color red hsl(0 50% 50%)
seed 24601
brush circles {
random.xy0(num: 50).each {
circle(x, y, r: random(0.1), fill: black)
}
}
pathprofile mytaper {
0.0
1.0
0.8
0.6
0.4
0.2
0.1
}
layer.svg some shapes {
subtract {
circle(x: $width/2, y: $height/2, r: $height/2*.8, fill: red)
rectangle(x: random.x, y: random.y, w: random($width - $x), h: 50, fill: red.lighten(0.2))
}
mask {
line(x1: random.x, y1: random.y, x2: random.x, y2: random.y, strokeWidth: random(5, 20), brush: circles)
}
blend soft-light
}
layer.raster noise {
xy.each {
red.jitter(hue: 0.2)
}
blend soft-light
opacity 0.2
}
Notes, acknowledging that this is mostly brainstorming and not a final, consolidated list:
- Layers are the fundamental block, in that nothing gets output if there aren’t any layers.
- Layers can be SVG (which then gets rendered to raster once the layer is done) or raster.
- The
background
property adds a bottom layer filled with the color (leaving it out allows for transparency). - There’s a debug flag that exports each layer to a file individually (minus blending mode and opacity), so you can see what happened.
- SVG layer commands include CSG booleans.
- The “some shapes” and “noise” bits on the layers are layer names.
- This isn’t shown here, but layers can be different sizes and can be positioned.
- I’m leaning towards having Marks be as purely declarative as possible, though I suspect I’ll still need some imperative support (a random walk, for example, would be more difficult to do declaratively) (though I’m still going to see if I can make it work somehow). I’m thinking the top-level
function
blocks would be where imperative code happens, maybe. random.x
would get a random X coordinate within the canvas.- Layers can be masked, and within the mask there can be any rendering commands (still working on nomenclature here) that are available for the current layer type (which mostly means raster commands aren’t available on SVG layers except in places where an image is expected).
- For SVG layers, the layer blending mode and opacity only apply after rasterization.
- The
xy
variable returns an array of all the coordinates of the rasterized canvas. - Numbers and colors have a
jitter
method that allows for jittering within a set range . - The
brush
block allows using rendering commands to create a brush that can then be used to stroke paths or fill shapes. - The
pathprofile
block needs (a lot of) work, but the idea is to have some way to describe the profile of a path, so you can have paths with the width varying along the stroke. Ideally, this would support both relative percentage-based profiles (leading to different effects for short paths vs. long paths) and absolute profiles (so the effect is the same for any length of path that matches the values) and possibly a mix of both (absolute for the first 50px and then relatively fade out for the rest, however long that is, for example). - Good support for path navigation and manipulation. Be able to easily work with points every 20px along the path, for example, or every control point, or fifty random points along the path. Also be able to get normals and tangents for any points along the path.
- Importing images from disk will of course be included.
- This is a lower priority for me: animation support. There would be access to a
frame
variable. Each frame could use the same global random seed (with each layer getting seeded individually to try to preserve behavior) or could use a different seed. - This is moving more into pie in the sky territory, but I’ve become enamored of the idea of having Marks support 3D, including programmatic generation of the canvas texture in 3D and being able to layer paint (and then scrape some off to see layers underneath) and get impasto effects and all that. This would also support moving the camera around and creating/modifying 3D models to some extent. I like this a lot and I think it fits conceptually, but it may be a bit Too Much. (Part of me thinks it would make far more sense to do this separately as something in Blender, where you get so many 3D features for free.)
- Marks files can import other Marks files.
- Custom fill functions can be written, where you provide some code that then generates the content for whatever polygon is filled (clipped to the polygon boundary).
- Shape packing helpers are planned, too, so you can fill a polygon with circles or any other shape. I’d like this to be programmatic, so that each shape could be different.
- Layer filters will be included, too — blur, threshold, erosion/dilation, etc. — and these will apply to masks as well.
- Colors can be defined in the usual formats (HSL, RGB, hex) and also LCH, and they can be modified dynamically (lightened, darkened, etc.).
There will undoubtedly be simplification along the way, since this is…a lot. The initial core implementation would (in my mind) be layers (both SVG and render, with a basic set of commands, excluding masks but including blending modes and opacity), colors, the random generator, debug mode, and exporting a final image. That’s enough base functionality for it to be usable.
I’m going to be working on a BNF grammar for this soon, once I get the syntax finalized enough that writing a parser is feasible.