primitive 1.0.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/optimize.js DELETED
@@ -1,53 +0,0 @@
1
- import State from './state'
2
-
3
- export const hillClimb = (state, maxAge) => {
4
- let bestState = state
5
- let bestEnergy = state.energy()
6
-
7
- for (let age = 0; age < maxAge; ++age) {
8
- const newState = bestState.mutate()
9
- const newEnergy = newState.energy()
10
-
11
- if (newEnergy < bestEnergy) {
12
- bestEnergy = newEnergy
13
- bestState = newState
14
- age = -1
15
- }
16
- }
17
-
18
- return bestState
19
- }
20
-
21
- export const getBestHillClimbState = (worker, opts) => {
22
- const state = getBestRandomState(worker, opts)
23
- return hillClimb(state, opts.numCandidateMutations)
24
- }
25
-
26
- export const getBestRandomState = (worker, opts) => {
27
- const {
28
- numCandidateShapes,
29
- shapeType,
30
- shapeAlpha
31
- } = opts
32
-
33
- let bestEnergy = null
34
- let bestState = null
35
-
36
- for (let i = 0; i < numCandidateShapes; ++i) {
37
- const state = State.create(worker, shapeType, shapeAlpha)
38
- const energy = state.energy()
39
-
40
- if (!i || energy < bestEnergy) {
41
- bestEnergy = energy
42
- bestState = state
43
- }
44
- }
45
-
46
- return bestState
47
- }
48
-
49
- export default {
50
- hillClimb,
51
- getBestHillClimbState,
52
- getBestRandomState
53
- }
package/lib/primitive.js DELETED
@@ -1,76 +0,0 @@
1
- import ow from 'ow'
2
-
3
- import core from './core'
4
- import Model from './model'
5
-
6
- const noop = () => { }
7
-
8
- export default async (opts) => {
9
- const {
10
- context,
11
- target,
12
- onStep,
13
-
14
- // inputSize = undefined, // TODO: support resizing target
15
- outputSize = undefined,
16
-
17
- minEnergy = undefined,
18
-
19
- shapeAlpha = 128,
20
- shapeType = 'triangle',
21
-
22
- numCandidates = 1, // [ 1, 32 ]
23
- numCandidateShapes = 50, // [ 10, 300 ]
24
- numCandidateMutations = 100, // [ 10, 500 ]
25
- numCandidateExtras = 0, // [ 0, 16 ]
26
-
27
- log = noop
28
- } = opts
29
-
30
- // validate options
31
- ow(opts, ow.object.plain.label('opts'))
32
- ow(target, ow.object.label('target'))
33
- ow(target.width, ow.number.positive.integer.label('target.width'))
34
- ow(target.height, ow.number.positive.integer.label('target.height'))
35
- ow(target.data, ow.any(ow.uint8Array, ow.uint8ClampedArray))
36
- ow(shapeAlpha, ow.number.integer.greaterThanOrEqual(0).lessThanOrEqual(255).label('shapeAlpha'))
37
- ow(shapeType, ow.string.nonEmpty.label('shapeType'))
38
- ow(numCandidates, ow.number.integer.positive.label('numCandidates'))
39
- ow(numCandidateShapes, ow.number.integer.positive.label('numCandidateShapes'))
40
- ow(numCandidateMutations, ow.number.integer.positive.label('numCandidateMutations'))
41
- ow(log, ow.function.label('log'))
42
- ow(onStep, ow.function.label('onStep'))
43
-
44
- const backgroundColor = core.getMeanColor(target)
45
-
46
- const model = new Model({
47
- context,
48
- target,
49
- backgroundColor,
50
- outputSize,
51
- numCandidates
52
- })
53
-
54
- const step = async (index) => {
55
- await onStep(model, step)
56
-
57
- const candidates = model.step({
58
- shapeType,
59
- shapeAlpha,
60
- numCandidateShapes,
61
- numCandidateMutations,
62
- numCandidateExtras
63
- })
64
-
65
- if (minEnergy && model.score <= minEnergy) {
66
- return false
67
- }
68
-
69
- return candidates
70
- }
71
-
72
- return {
73
- model,
74
- step
75
- }
76
- }
package/lib/rasterize.js DELETED
@@ -1,101 +0,0 @@
1
- import Scanline from './scanline'
2
-
3
- /**
4
- * Converts a polygon to an array of rasterizable scanlines.
5
- *
6
- * @param {Array<Object>} points
7
- * @return {Array<Scanline>}
8
- */
9
- export default (points) => {
10
- const lines = []
11
- let edges = []
12
-
13
- for (let i = 0; i < points.length; ++i) {
14
- const p1 = points[i]
15
- const p2 = (i >= points.length - 1 ? points[0] : points[i + 1])
16
- const p1p2 = bresenham(p1.x | 0, p1.y | 0, p2.x | 0, p2.y | 0)
17
- edges = edges.concat(p1p2)
18
- }
19
-
20
- const yToXs = new Map()
21
- for (let i = 0; i < edges.length; ++i) {
22
- const point = edges[i]
23
- let xSet = yToXs.get(point.y)
24
-
25
- if (xSet) {
26
- xSet.add(point.x)
27
- } else {
28
- xSet = new Set()
29
- xSet.add(point.x)
30
- yToXs.set(point.y, xSet)
31
- }
32
- }
33
-
34
- yToXs.forEach((xSet, y) => {
35
- const minMax = minMaxElements(xSet)
36
-
37
- if (minMax) {
38
- lines.push(new Scanline(y, minMax.min, minMax.max))
39
- }
40
- })
41
-
42
- return lines
43
- }
44
-
45
- function bresenham (x1, y1, x2, y2) {
46
- const points = []
47
-
48
- let dx = x2 - x1
49
- const ix = Math.sign(dx)
50
- dx = Math.abs(dx) * 2
51
-
52
- let dy = y2 - y1
53
- const iy = Math.sign(dy)
54
- dy = Math.abs(dy) * 2
55
-
56
- points.push({ x: x1, y: y1 })
57
-
58
- if (dx >= dy) {
59
- let error = (dy - (dx >> 1))
60
-
61
- while (x1 !== x2) {
62
- if (error >= 0 && (error !== 0 || ix > 0)) {
63
- error -= dx
64
- y1 += iy
65
- }
66
-
67
- error += dy
68
- x1 += ix
69
- points.push({ x: x1, y: y1 })
70
- }
71
- } else {
72
- let error = (dx - (dy >> 1))
73
-
74
- while (y1 !== y2) {
75
- if (error >= 0 && (error !== 0 || iy > 0)) {
76
- error -= dy
77
- x1 += ix
78
- }
79
-
80
- error += dx
81
- y1 += iy
82
- points.push({ x: x1, y: y1 })
83
- }
84
- }
85
-
86
- return points
87
- }
88
-
89
- function minMaxElements (iterable) {
90
- let min = null
91
- let max = null
92
-
93
- for (let v of iterable) {
94
- if (!min || v < min) min = v
95
- if (!max || v > max) max = v
96
- }
97
-
98
- if (min !== null && max !== null) {
99
- return { min, max }
100
- }
101
- }
package/lib/scanline.js DELETED
@@ -1,25 +0,0 @@
1
- export default class Scanline {
2
- constructor (y, x1, x2) {
3
- if (x1 > x2) {
4
- let t = x1
5
- x1 = x2
6
- x2 = t
7
- }
8
-
9
- this.y = y | 0
10
- this.x1 = x1 | 0
11
- this.x2 = x2 | 0
12
- }
13
-
14
- static filter (lines, width, height) {
15
- return lines.filter((line) => {
16
- if (line.y < 0 || line.y >= height) return false
17
- if (line.x1 >= width || line.x2 < 0) return false
18
-
19
- line.x1 = Math.max(0, Math.min(width - 1, line.x1))
20
- line.x2 = Math.max(0, Math.min(width - 1, line.x2))
21
-
22
- return (line.x1 < line.x2)
23
- })
24
- }
25
- }
@@ -1,112 +0,0 @@
1
- import randomInt from 'random-int'
2
- import randomNormal from 'random-normal'
3
-
4
- import Scanline from '../scanline'
5
- import Shape from './shape'
6
-
7
- export default class Ellipse extends Shape {
8
- constructor (opts) {
9
- super(opts)
10
- if (!opts) return
11
-
12
- this.circle = !!opts.circle
13
- this.x = randomInt(0, this.width - 1)
14
- this.y = randomInt(0, this.height - 1)
15
-
16
- this.rx = randomInt(1, 32)
17
- this.ry = this.circle
18
- ? this.rx
19
- : randomInt(1, 32)
20
- }
21
-
22
- copy () {
23
- const shape = new Ellipse()
24
- shape.width = this.width
25
- shape.height = this.height
26
- shape.circle = this.circle
27
- shape.x = this.x
28
- shape.y = this.y
29
- shape.rx = this.rx
30
- shape.ry = this.ry
31
- return shape
32
- }
33
-
34
- bounds () {
35
- let { x1, y1, x2, y2 } = this
36
- let t
37
-
38
- if (x1 > x2) {
39
- t = x1
40
- x1 = x2
41
- x2 = t
42
- }
43
-
44
- if (y1 > y2) {
45
- t = y1
46
- y1 = y2
47
- y2 = t
48
- }
49
-
50
- return { x1, y1, x2, y2 }
51
- }
52
-
53
- mutate () {
54
- const { width, height } = this
55
- const shape = this.copy()
56
- const m = 16
57
-
58
- switch (randomInt(0, 2)) {
59
- case 0:
60
- shape.x = Math.max(0, Math.min(width - 1, (shape.x + randomNormal() * m)))
61
- shape.y = Math.max(0, Math.min(height - 1, (shape.y + randomNormal() * m)))
62
- break
63
-
64
- case 1:
65
- shape.rx = Math.max(1, Math.min(width - 1, (shape.rx + randomNormal() * m)))
66
- if (shape.circle) shape.ry = shape.rx
67
- break
68
-
69
- case 2:
70
- shape.ry = Math.max(1, Math.min(height - 1, (shape.ry + randomNormal() * m)))
71
- if (shape.circle) shape.rx = shape.ry
72
- break
73
- }
74
-
75
- return shape
76
- }
77
-
78
- rasterize () {
79
- const { width, height, x, y, rx, ry } = this
80
- const lines = []
81
- const aspect = rx / ry
82
-
83
- for (let dy = 0; dy < ry; ++dy) {
84
- const y1 = y - dy
85
- const y2 = y + dy
86
-
87
- if ((y1 < 0 || y1 >= height) && (y2 < 0 || y2 >= height)) {
88
- continue
89
- }
90
-
91
- const s = Math.sqrt(ry * ry - dy * dy) * aspect
92
- const x1 = Math.max(0, x - s)
93
- const x2 = Math.min(width - 1, x + s)
94
-
95
- if (y1 >= 0 && y1 < height) {
96
- lines.push(new Scanline(y1, x1, x2))
97
- }
98
-
99
- if (y2 >= 0 && y2 < height && dy > 0) {
100
- lines.push(new Scanline(y2, x1, x2))
101
- }
102
- }
103
-
104
- return lines
105
- }
106
-
107
- toSVG (attrs = '') {
108
- return (
109
- `<ellipse ${attrs} cx="${this.x}" cy="${this.y}" rx="${this.rx}" ry="${this.ry}" />`
110
- )
111
- }
112
- }
@@ -1,52 +0,0 @@
1
- import randomInt from 'random-int'
2
-
3
- import Ellipse from './ellipse'
4
- import Rectangle from './rectangle'
5
- import RotatedEllipse from './rotated-ellipse'
6
- import RotatedRectangle from './rotated-rectangle'
7
- import Triangle from './triangle'
8
-
9
- const SHAPES = [
10
- // ellipse and rectangle are redundant with their rotated versions
11
- // 'ellipse',
12
- // 'rectangle',
13
- 'rotated-ellipse',
14
- 'rotated-rectangle',
15
- 'triangle'
16
- ]
17
-
18
- const factory = (shapeType, opts) => {
19
- switch (shapeType) {
20
- case 'rectangle':
21
- case 'rect':
22
- return new Rectangle(opts)
23
-
24
- case 'rotated-rectangle':
25
- case 'rotated-rect':
26
- return new RotatedRectangle(opts)
27
-
28
- case 'circle':
29
- return new Ellipse({
30
- circle: true,
31
- ...opts
32
- })
33
-
34
- case 'ellipse':
35
- return new Ellipse(opts)
36
-
37
- case 'rotated-ellipse':
38
- return new RotatedEllipse(opts)
39
-
40
- case 'triangle':
41
- return new Triangle(opts)
42
-
43
- default:
44
- return factory(random(), opts)
45
- }
46
- }
47
-
48
- function random () {
49
- return SHAPES[randomInt(SHAPES.length)]
50
- }
51
-
52
- export default factory
@@ -1,93 +0,0 @@
1
- import randomInt from 'random-int'
2
- import randomNormal from 'random-normal'
3
-
4
- import Scanline from '../scanline'
5
- import Shape from './shape'
6
-
7
- export default class Rectangle extends Shape {
8
- constructor (opts) {
9
- super(opts)
10
- if (!opts) return
11
-
12
- this.x1 = randomInt(0, this.width - 1)
13
- this.y1 = randomInt(0, this.height - 1)
14
-
15
- this.x2 = Math.max(0, Math.min(this.width - 1, this.x1 + randomInt(-16, 16)))
16
- this.y2 = Math.max(0, Math.min(this.height - 1, this.y1 + randomInt(-16, 16)))
17
- }
18
-
19
- copy () {
20
- const shape = new Rectangle()
21
- shape.width = this.width
22
- shape.height = this.height
23
- shape.x1 = this.x1
24
- shape.y1 = this.y1
25
- shape.x2 = this.x2
26
- shape.y2 = this.y2
27
- return shape
28
- }
29
-
30
- bounds () {
31
- let { x1, y1, x2, y2 } = this
32
- let t
33
-
34
- if (x1 > x2) {
35
- t = x1
36
- x1 = x2
37
- x2 = t
38
- }
39
-
40
- if (y1 > y2) {
41
- t = y1
42
- y1 = y2
43
- y2 = t
44
- }
45
-
46
- return { x1, y1, x2, y2 }
47
- }
48
-
49
- mutate () {
50
- const { width, height } = this
51
- const shape = this.copy()
52
- const m = 16
53
-
54
- switch (randomInt(0, 1)) {
55
- case 0:
56
- shape.x1 = Math.max(0, Math.min(width - 1, (shape.x1 + randomNormal() * m)))
57
- shape.y1 = Math.max(0, Math.min(height - 1, (shape.y1 + randomNormal() * m)))
58
- break
59
-
60
- case 1:
61
- shape.x2 = Math.max(0, Math.min(width - 1, (shape.x2 + randomNormal() * m)))
62
- shape.y2 = Math.max(0, Math.min(height - 1, (shape.y2 + randomNormal() * m)))
63
- break
64
- }
65
-
66
- return shape
67
- }
68
-
69
- rasterize () {
70
- const { x1, y1, x2, y2 } = this.bounds()
71
- const lines = []
72
-
73
- for (let y = y1; y <= y2; ++y) {
74
- lines.push(new Scanline(y, x1, x2))
75
- }
76
-
77
- return lines
78
- }
79
-
80
- draw (ctx) {
81
- // TODO
82
- }
83
-
84
- toSVG (attrs = '') {
85
- const { x1, y1, x2, y2 } = this.bounds()
86
- const w = x2 - x1 + 1
87
- const h = y2 - y1 + 1
88
-
89
- return (
90
- `<rect ${attrs} x="${x1}" y="${y1}" width="${w}" height="${h}" />`
91
- )
92
- }
93
- }
@@ -1,99 +0,0 @@
1
- import randomInt from 'random-int'
2
- import randomNormal from 'random-normal'
3
-
4
- import rasterize from '../rasterize'
5
- import Scanline from '../scanline'
6
- import Shape from './shape'
7
-
8
- export default class RotatedEllipse extends Shape {
9
- constructor (opts) {
10
- super(opts)
11
- if (!opts) return
12
-
13
- this.x = randomInt(0, this.width - 1)
14
- this.y = randomInt(0, this.height - 1)
15
-
16
- this.rx = randomInt(1, 32)
17
- this.ry = randomInt(1, 32)
18
-
19
- this.angle = Math.random() * 360
20
- }
21
-
22
- copy () {
23
- const shape = new RotatedEllipse()
24
- shape.width = this.width
25
- shape.height = this.height
26
- shape.x = this.x
27
- shape.y = this.y
28
- shape.rx = this.rx
29
- shape.ry = this.ry
30
- shape.angle = this.angle
31
- return shape
32
- }
33
-
34
- mutate () {
35
- const { width, height } = this
36
- const shape = this.copy()
37
- const m = 16
38
-
39
- switch (randomInt(0, 2)) {
40
- case 0:
41
- shape.x = Math.max(0, Math.min(width - 1, (shape.x + randomNormal() * m)))
42
- shape.y = Math.max(0, Math.min(height - 1, (shape.y + randomNormal() * m)))
43
- break
44
-
45
- case 1:
46
- shape.rx = Math.max(1, Math.min(width - 1, (shape.rx + randomNormal() * m)))
47
- shape.ry = Math.max(1, Math.min(height - 1, (shape.ry + randomNormal() * m)))
48
- break
49
-
50
- case 2:
51
- shape.angle = shape.angle + randomNormal() * m
52
- break
53
- }
54
-
55
- return shape
56
- }
57
-
58
- rasterize () {
59
- const points = this.getPoints()
60
- const lines = rasterize(points)
61
- return Scanline.filter(lines, this.width, this.height)
62
- }
63
-
64
- getPoints (numPoints = 20) {
65
- const { x, y, rx, ry, angle } = this
66
- const points = []
67
- const rads = angle * Math.PI / 180.0
68
- const c = Math.cos(rads)
69
- const s = Math.sin(rads)
70
-
71
- for (let i = 0; i < numPoints; ++i) {
72
- const rot = ((360.0 / numPoints) * i) * (Math.PI / 180.0)
73
- const crx = rx * Math.cos(rot)
74
- const cry = ry * Math.sin(rot)
75
-
76
- const tx = (crx * c - cry * s + x) | 0
77
- const ty = (crx * s + cry * c + y) | 0
78
-
79
- points.push({ x: tx, y: ty })
80
- }
81
-
82
- return points
83
- }
84
-
85
- draw (ctx) {
86
- throw new Error('TODO')
87
- }
88
-
89
- toSVG (attrs = '') {
90
- // TODO: native rotated ellipse will produce smaller and smoother results
91
- const points = this.getPoints()
92
- .map((point) => `${point.x} ${point.y}`)
93
- .join(' ')
94
-
95
- return (
96
- `<polygon ${attrs} points="${points}" />`
97
- )
98
- }
99
- }