-
Notifications
You must be signed in to change notification settings - Fork 18
/
Copy pathgenerative-paint.js
125 lines (107 loc) · 3.96 KB
/
generative-paint.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/*
This is a more advanced example of a generative/algorithmic
print created with `penplot` and Canvas2D. It mostly consists of
concentric circles with different arc lengths and a lot of
random offsets.
*/
import newArray from 'new-array';
import clamp from 'clamp';
import { PaperSize, Orientation } from 'penplot';
import * as RND from 'penplot/util/random';
import allPalettes from 'nice-color-palettes/500';
import colorConvert from 'color-convert';
import fromCSS from 'css-color-converter';
// often it's handy to mark down a nice seed so you can re-print it later
// at a different size
const seed = '42032';
// other times you may want to idly browse through random seeds
// const seed = String(Math.floor(Math.random() * 100000));
// check the console for the seed number :)
console.log('Seed:', seed);
RND.setSeed(seed);
const palettes = [
[ 'hsl(0, 0%, 85%)', 'hsl(0, 0%, 95%)' ],
RND.shuffle(RND.shuffle(allPalettes)[0]).slice(0, 3)
];
export const orientation = Orientation.PORTRAIT;
export const dimensions = PaperSize.SKETCHBOOK;
export default function createPlot (context, dimensions) {
const [ width, height ] = dimensions;
const lineCount = 300;
const segments = 2000;
const corePalette = palettes[0];
const altPalette = palettes[1];
const allPoints = [];
const lines = newArray(lineCount).map((_, j) => {
const radius = 0;
const angleOffset = RND.randomFloat(-Math.PI * 2, Math.PI * 2);
const angleScale = RND.randomFloat(0.01, 0.01);
const pal = (RND.randomFloat(1) > 0.75) ? altPalette : corePalette;
const startColor = pal[RND.randomInt(pal.length)];
const hsl = colorConvert.hex.hsl(fromCSS(startColor).toHexString().replace(/^#/, ''));
const color = startColor;
// only modify the color palettes
const isHSLMod = pal === altPalette;
return {
lineWidth: RND.randomFloat(1) > 0.25 ? RND.randomFloat(0.01, 0.05) : RND.randomFloat(0.01, 4),
color,
hsl,
isHSLMod,
alpha: RND.randomFloat(0.15, 0.75),
points: newArray(segments).map((_, i) => {
const t = i / (segments - 1);
const K = j / (lineCount - 1);
const angleOff = RND.noise2D(K * 1, t * 200) * 0.1;
const angle = (Math.PI * 2 * t) * angleScale + angleOff + angleOffset;
const x = Math.cos(angle);
const y = Math.sin(angle);
const offset = j * (0.2 + RND.randomFloat(-1, RND.randomFloat(0, 5)) * 0.1) * 0.5;
const r = radius + offset;
const center = RND.randomCircle([], 0.01)
const point = [ x * r + width / 2 + center[0], y * r + height / 2 + center[1] ];
const f = 10;
const amp = 0.005;
point[0] += RND.noise2D(f * point[0], f * point[1], f * 1000) * amp;
point[1] += RND.noise2D(f * point[0], f * point[1], f * -1000) * amp;
const newColor = isHSLMod
? `#${colorConvert.hsl.hex(offsetLightness(hsl, RND.randomFloat(-1, 1) * 10))}`
: startColor;
allPoints.push(point);
return {
position: point,
color: newColor
};
})
};
});
return {
draw,
outputSize: '300 dpi', // render as print resolution instead of web
background: corePalette[0]
};
function offsetLightness (hsl, l) {
hsl = hsl.slice();
hsl[2] += l;
hsl[2] = clamp(hsl[2], 0, 100);
return hsl;
}
function draw () {
lines.forEach(line => {
// render each line as small segments so we get overlapping
// opacities
for (let i = 0; i < line.points.length / 2; i++) {
context.beginPath();
context.globalAlpha = line.alpha;
context.lineWidth = line.lineWidth;
context.lineJoin = 'round';
context.lineCap = 'square';
context.strokeStyle = line.points[i * 2 + 0].color;
const [ x1, y1 ] = line.points[i * 2 + 0].position;
const [ x2, y2 ] = line.points[i * 2 + 1].position;
context.lineTo(x1, y1);
context.lineTo(x2, y2);
context.stroke();
}
});
}
}