primitive 1.0.1 → 1.4.0

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/random.js DELETED
@@ -1,4 +0,0 @@
1
- import random from 'random'
2
- export default random
3
-
4
- export const normal = random.normal()
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,111 +0,0 @@
1
- import random, { normal } from '../random'
2
-
3
- import Scanline from '../scanline'
4
- import Shape from './shape'
5
-
6
- export default class Ellipse extends Shape {
7
- constructor (opts) {
8
- super(opts)
9
- if (!opts) return
10
-
11
- this.circle = !!opts.circle
12
- this.x = random.int(0, this.width - 1)
13
- this.y = random.int(0, this.height - 1)
14
-
15
- this.rx = random.int(1, 32)
16
- this.ry = this.circle
17
- ? this.rx
18
- : random.int(1, 32)
19
- }
20
-
21
- copy () {
22
- const shape = new Ellipse()
23
- shape.width = this.width
24
- shape.height = this.height
25
- shape.circle = this.circle
26
- shape.x = this.x
27
- shape.y = this.y
28
- shape.rx = this.rx
29
- shape.ry = this.ry
30
- return shape
31
- }
32
-
33
- bounds () {
34
- let { x1, y1, x2, y2 } = this
35
- let t
36
-
37
- if (x1 > x2) {
38
- t = x1
39
- x1 = x2
40
- x2 = t
41
- }
42
-
43
- if (y1 > y2) {
44
- t = y1
45
- y1 = y2
46
- y2 = t
47
- }
48
-
49
- return { x1, y1, x2, y2 }
50
- }
51
-
52
- mutate () {
53
- const { width, height } = this
54
- const shape = this.copy()
55
- const m = 16
56
-
57
- switch (random.int(0, 2)) {
58
- case 0:
59
- shape.x = Math.max(0, Math.min(width - 1, (shape.x + normal() * m)))
60
- shape.y = Math.max(0, Math.min(height - 1, (shape.y + normal() * m)))
61
- break
62
-
63
- case 1:
64
- shape.rx = Math.max(1, Math.min(width - 1, (shape.rx + normal() * m)))
65
- if (shape.circle) shape.ry = shape.rx
66
- break
67
-
68
- case 2:
69
- shape.ry = Math.max(1, Math.min(height - 1, (shape.ry + normal() * m)))
70
- if (shape.circle) shape.rx = shape.ry
71
- break
72
- }
73
-
74
- return shape
75
- }
76
-
77
- rasterize () {
78
- const { width, height, x, y, rx, ry } = this
79
- const lines = []
80
- const aspect = rx / ry
81
-
82
- for (let dy = 0; dy < ry; ++dy) {
83
- const y1 = y - dy
84
- const y2 = y + dy
85
-
86
- if ((y1 < 0 || y1 >= height) && (y2 < 0 || y2 >= height)) {
87
- continue
88
- }
89
-
90
- const s = Math.sqrt(ry * ry - dy * dy) * aspect
91
- const x1 = Math.max(0, x - s)
92
- const x2 = Math.min(width - 1, x + s)
93
-
94
- if (y1 >= 0 && y1 < height) {
95
- lines.push(new Scanline(y1, x1, x2))
96
- }
97
-
98
- if (y2 >= 0 && y2 < height && dy > 0) {
99
- lines.push(new Scanline(y2, x1, x2))
100
- }
101
- }
102
-
103
- return lines
104
- }
105
-
106
- toSVG (attrs = '') {
107
- return (
108
- `<ellipse ${attrs} cx="${this.x}" cy="${this.y}" rx="${this.rx}" ry="${this.ry}" />`
109
- )
110
- }
111
- }
@@ -1,52 +0,0 @@
1
- import random from '../random'
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(randomShapeType(), opts)
45
- }
46
- }
47
-
48
- function randomShapeType () {
49
- return SHAPES[random.int(SHAPES.length - 1)]
50
- }
51
-
52
- export default factory
@@ -1,92 +0,0 @@
1
- import random, { normal } from '../random'
2
-
3
- import Scanline from '../scanline'
4
- import Shape from './shape'
5
-
6
- export default class Rectangle extends Shape {
7
- constructor (opts) {
8
- super(opts)
9
- if (!opts) return
10
-
11
- this.x1 = random.int(0, this.width - 1)
12
- this.y1 = random.int(0, this.height - 1)
13
-
14
- this.x2 = Math.max(0, Math.min(this.width - 1, this.x1 + random.int(-16, 16)))
15
- this.y2 = Math.max(0, Math.min(this.height - 1, this.y1 + random.int(-16, 16)))
16
- }
17
-
18
- copy () {
19
- const shape = new Rectangle()
20
- shape.width = this.width
21
- shape.height = this.height
22
- shape.x1 = this.x1
23
- shape.y1 = this.y1
24
- shape.x2 = this.x2
25
- shape.y2 = this.y2
26
- return shape
27
- }
28
-
29
- bounds () {
30
- let { x1, y1, x2, y2 } = this
31
- let t
32
-
33
- if (x1 > x2) {
34
- t = x1
35
- x1 = x2
36
- x2 = t
37
- }
38
-
39
- if (y1 > y2) {
40
- t = y1
41
- y1 = y2
42
- y2 = t
43
- }
44
-
45
- return { x1, y1, x2, y2 }
46
- }
47
-
48
- mutate () {
49
- const { width, height } = this
50
- const shape = this.copy()
51
- const m = 16
52
-
53
- switch (random.int(0, 1)) {
54
- case 0:
55
- shape.x1 = Math.max(0, Math.min(width - 1, (shape.x1 + normal() * m)))
56
- shape.y1 = Math.max(0, Math.min(height - 1, (shape.y1 + normal() * m)))
57
- break
58
-
59
- case 1:
60
- shape.x2 = Math.max(0, Math.min(width - 1, (shape.x2 + normal() * m)))
61
- shape.y2 = Math.max(0, Math.min(height - 1, (shape.y2 + normal() * m)))
62
- break
63
- }
64
-
65
- return shape
66
- }
67
-
68
- rasterize () {
69
- const { x1, y1, x2, y2 } = this.bounds()
70
- const lines = []
71
-
72
- for (let y = y1; y <= y2; ++y) {
73
- lines.push(new Scanline(y, x1, x2))
74
- }
75
-
76
- return lines
77
- }
78
-
79
- draw (ctx) {
80
- // TODO
81
- }
82
-
83
- toSVG (attrs = '') {
84
- const { x1, y1, x2, y2 } = this.bounds()
85
- const w = x2 - x1 + 1
86
- const h = y2 - y1 + 1
87
-
88
- return (
89
- `<rect ${attrs} x="${x1}" y="${y1}" width="${w}" height="${h}" />`
90
- )
91
- }
92
- }
@@ -1,98 +0,0 @@
1
- import random, { normal } from '../random'
2
-
3
- import rasterize from '../rasterize'
4
- import Scanline from '../scanline'
5
- import Shape from './shape'
6
-
7
- export default class RotatedEllipse extends Shape {
8
- constructor (opts) {
9
- super(opts)
10
- if (!opts) return
11
-
12
- this.x = random.int(0, this.width - 1)
13
- this.y = random.int(0, this.height - 1)
14
-
15
- this.rx = random.int(1, 32)
16
- this.ry = random.int(1, 32)
17
-
18
- this.angle = random.float() * 360
19
- }
20
-
21
- copy () {
22
- const shape = new RotatedEllipse()
23
- shape.width = this.width
24
- shape.height = this.height
25
- shape.x = this.x
26
- shape.y = this.y
27
- shape.rx = this.rx
28
- shape.ry = this.ry
29
- shape.angle = this.angle
30
- return shape
31
- }
32
-
33
- mutate () {
34
- const { width, height } = this
35
- const shape = this.copy()
36
- const m = 16
37
-
38
- switch (random.int(0, 2)) {
39
- case 0:
40
- shape.x = Math.max(0, Math.min(width - 1, (shape.x + normal() * m)))
41
- shape.y = Math.max(0, Math.min(height - 1, (shape.y + normal() * m)))
42
- break
43
-
44
- case 1:
45
- shape.rx = Math.max(1, Math.min(width - 1, (shape.rx + normal() * m)))
46
- shape.ry = Math.max(1, Math.min(height - 1, (shape.ry + normal() * m)))
47
- break
48
-
49
- case 2:
50
- shape.angle = shape.angle + normal() * m
51
- break
52
- }
53
-
54
- return shape
55
- }
56
-
57
- rasterize () {
58
- const points = this.getPoints()
59
- const lines = rasterize(points)
60
- return Scanline.filter(lines, this.width, this.height)
61
- }
62
-
63
- getPoints (numPoints = 20) {
64
- const { x, y, rx, ry, angle } = this
65
- const points = []
66
- const rads = angle * Math.PI / 180.0
67
- const c = Math.cos(rads)
68
- const s = Math.sin(rads)
69
-
70
- for (let i = 0; i < numPoints; ++i) {
71
- const rot = ((360.0 / numPoints) * i) * (Math.PI / 180.0)
72
- const crx = rx * Math.cos(rot)
73
- const cry = ry * Math.sin(rot)
74
-
75
- const tx = (crx * c - cry * s + x) | 0
76
- const ty = (crx * s + cry * c + y) | 0
77
-
78
- points.push({ x: tx, y: ty })
79
- }
80
-
81
- return points
82
- }
83
-
84
- draw (ctx) {
85
- throw new Error('TODO')
86
- }
87
-
88
- toSVG (attrs = '') {
89
- // TODO: native rotated ellipse will produce smaller and smoother results
90
- const points = this.getPoints()
91
- .map((point) => `${point.x} ${point.y}`)
92
- .join(' ')
93
-
94
- return (
95
- `<polygon ${attrs} points="${points}" />`
96
- )
97
- }
98
- }