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/README.md +89 -0
- package/bin/run.js +16 -0
- package/dist/cli-config-B5hrwe8q.js +1330 -0
- package/dist/oclif/index.js +22785 -0
- package/dist/oclif/proxy-auto-detect.js +71 -0
- package/dist/oclif/root-signup-hint.js +136 -0
- package/man/primitive.1 +111 -0
- package/package.json +131 -67
- package/.babelrc +0 -12
- package/.editorconfig +0 -9
- package/.eslintrc +0 -5
- package/.travis.yml +0 -5
- package/browser.js +0 -109
- package/dist/browser.js +0 -5447
- package/lib/browser-context.js +0 -98
- package/lib/color.js +0 -6
- package/lib/context.js +0 -101
- package/lib/context.test.js +0 -31
- package/lib/core.js +0 -199
- package/lib/core.test.js +0 -70
- package/lib/model.js +0 -168
- package/lib/optimize.js +0 -53
- package/lib/primitive.js +0 -76
- package/lib/rasterize.js +0 -101
- package/lib/scanline.js +0 -25
- package/lib/shapes/ellipse.js +0 -112
- package/lib/shapes/factory.js +0 -52
- package/lib/shapes/rectangle.js +0 -93
- package/lib/shapes/rotated-ellipse.js +0 -99
- package/lib/shapes/rotated-rectangle.js +0 -126
- package/lib/shapes/shape.js +0 -26
- package/lib/shapes/triangle.js +0 -217
- package/lib/shapes/triangle.test.js +0 -73
- package/lib/state.js +0 -69
- package/lib/worker.js +0 -50
- package/main.js +0 -6
- package/main.test.js +0 -39
- package/module.js +0 -126
- package/notes.md +0 -15
- package/readme.md +0 -154
- package/rollup.config.js +0 -28
package/lib/browser-context.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import ow from 'ow'
|
|
2
|
-
|
|
3
|
-
import { cssrgba } from './color'
|
|
4
|
-
|
|
5
|
-
export const PARTIALS = true
|
|
6
|
-
export const platform = 'browser'
|
|
7
|
-
|
|
8
|
-
export const loadImage = async (input) => {
|
|
9
|
-
ow(input, ow.any(
|
|
10
|
-
ow.string.nonEmpty.label('input'),
|
|
11
|
-
ow.object.instanceOf(global.ImageData).label('input'),
|
|
12
|
-
ow.object.instanceOf(global.Image).label('input')
|
|
13
|
-
))
|
|
14
|
-
|
|
15
|
-
if (typeof input === 'string') {
|
|
16
|
-
const img = new global.Image()
|
|
17
|
-
img.crossOrigin = 'anonymous'
|
|
18
|
-
|
|
19
|
-
await new Promise((resolve, reject) => {
|
|
20
|
-
img.onload = resolve
|
|
21
|
-
img.onerror = img.onabort = () => reject(new Error('image failed to load'))
|
|
22
|
-
img.src = input
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
return loadImage(img)
|
|
26
|
-
} else if (input instanceof global.ImageData) {
|
|
27
|
-
return input
|
|
28
|
-
} else if (input instanceof global.Image) {
|
|
29
|
-
input.crossOrigin = 'anonymous'
|
|
30
|
-
const canvas = document.createElement('canvas')
|
|
31
|
-
canvas.width = input.naturalWidth
|
|
32
|
-
canvas.height = input.naturalHeight
|
|
33
|
-
const ctx = canvas.getContext('2d')
|
|
34
|
-
ctx.drawImage(input, 0, 0)
|
|
35
|
-
return ctx.getImageData(0, 0, canvas.width, canvas.height)
|
|
36
|
-
} else {
|
|
37
|
-
throw new Error('invalid input image')
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export const loadCanvas = async (value, label = 'canvas') => {
|
|
42
|
-
ow(value, ow.any(
|
|
43
|
-
ow.string.nonEmpty.label(label),
|
|
44
|
-
ow.object.instanceOf(global.HTMLCanvasElement).label(label)
|
|
45
|
-
))
|
|
46
|
-
|
|
47
|
-
if (typeof value === 'string') {
|
|
48
|
-
const canvas = document.querySelector(value)
|
|
49
|
-
|
|
50
|
-
if (!canvas) {
|
|
51
|
-
throw new Error(`invalid ${label} selector "${value}" not found`)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (!(canvas instanceof global.HTMLCanvasElement)) {
|
|
55
|
-
throw new Error(`invalid ${label} selector "${value}" is not a canvas element`)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return canvas
|
|
59
|
-
} else {
|
|
60
|
-
return value
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export const enableContextAntialiasing = (ctx) => {
|
|
65
|
-
ctx.mozImageSmoothingQuality = 'high'
|
|
66
|
-
ctx.webkitImageSmoothingQuality = 'high'
|
|
67
|
-
ctx.msImageSmoothingQuality = 'high'
|
|
68
|
-
ctx.imageSmoothingQuality = 'high'
|
|
69
|
-
|
|
70
|
-
ctx.mozImageSmoothingEnabled = true
|
|
71
|
-
ctx.webkitImageSmoothingEnabled = true
|
|
72
|
-
ctx.msImageSmoothingEnabled = true
|
|
73
|
-
ctx.imageSmoothingEnabled = true
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export const createImage = (width, height, fillColor = undefined) => {
|
|
77
|
-
ow(width, ow.number.label('width').positive.integer)
|
|
78
|
-
ow(height, ow.number.label('height').positive.integer)
|
|
79
|
-
|
|
80
|
-
const canvas = document.createElement('canvas')
|
|
81
|
-
canvas.width = width
|
|
82
|
-
canvas.height = height
|
|
83
|
-
const ctx = canvas.getContext('2d')
|
|
84
|
-
if (fillColor) {
|
|
85
|
-
ctx.fillStyle = cssrgba(fillColor)
|
|
86
|
-
ctx.fillRect(0, 0, width, height)
|
|
87
|
-
}
|
|
88
|
-
return ctx.getImageData(0, 0, width, height)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export default {
|
|
92
|
-
PARTIALS,
|
|
93
|
-
platform,
|
|
94
|
-
loadImage,
|
|
95
|
-
loadCanvas,
|
|
96
|
-
enableContextAntialiasing,
|
|
97
|
-
createImage
|
|
98
|
-
}
|
package/lib/color.js
DELETED
package/lib/context.js
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import execa from 'execa'
|
|
2
|
-
import fs from 'fs'
|
|
3
|
-
import ndarray from 'ndarray'
|
|
4
|
-
import ow from 'ow'
|
|
5
|
-
import pify from 'pify'
|
|
6
|
-
import pump from 'pump-promise'
|
|
7
|
-
import getPixels from 'get-pixels'
|
|
8
|
-
import savePixels from 'save-pixels'
|
|
9
|
-
|
|
10
|
-
const getPixelsP = pify(getPixels)
|
|
11
|
-
|
|
12
|
-
export const PARTIALS = true
|
|
13
|
-
export const platform = 'node'
|
|
14
|
-
|
|
15
|
-
export const loadImage = async (input) => {
|
|
16
|
-
ow(input, ow.string.label('input').nonEmpty)
|
|
17
|
-
|
|
18
|
-
const result = await getPixelsP(input)
|
|
19
|
-
const { data, shape } = result
|
|
20
|
-
|
|
21
|
-
return {
|
|
22
|
-
data,
|
|
23
|
-
width: shape[0],
|
|
24
|
-
height: shape[1]
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export const createImage = (width, height, color = undefined) => {
|
|
29
|
-
ow(width, ow.number.label('width').positive.integer)
|
|
30
|
-
ow(height, ow.number.label('height').positive.integer)
|
|
31
|
-
|
|
32
|
-
const data = new Uint8ClampedArray(width * height * 4)
|
|
33
|
-
|
|
34
|
-
if (color) {
|
|
35
|
-
for (let i = 0; i < width * height; ++i) {
|
|
36
|
-
const o = i * 4
|
|
37
|
-
data[o + 0] = color.r
|
|
38
|
-
data[o + 1] = color.g
|
|
39
|
-
data[o + 2] = color.b
|
|
40
|
-
data[o + 3] = color.a
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
data,
|
|
46
|
-
width,
|
|
47
|
-
height
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export const saveImage = async (image, filename, opts) => {
|
|
52
|
-
ow(image, ow.object.label('image').nonEmpty)
|
|
53
|
-
ow(filename, ow.string.label('filename').nonEmpty)
|
|
54
|
-
|
|
55
|
-
const pixels = ndarray(image.data, [ image.height, image.width, 4 ])
|
|
56
|
-
const parts = filename.split('.')
|
|
57
|
-
const format = parts[parts.length - 1]
|
|
58
|
-
const stream = savePixels(pixels.transpose(1, 0, 2), format)
|
|
59
|
-
return pump(stream, fs.createWriteStream(filename))
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export const saveGIF = async (frames, filename, opts) => {
|
|
63
|
-
ow(frames, ow.array.label('frames'))
|
|
64
|
-
ow(filename, ow.string.label('filename').nonEmpty)
|
|
65
|
-
ow(opts, ow.object.label('opts').plain.nonEmpty)
|
|
66
|
-
|
|
67
|
-
const {
|
|
68
|
-
// gif output options
|
|
69
|
-
gifski = {
|
|
70
|
-
fps: 10,
|
|
71
|
-
quality: 80,
|
|
72
|
-
fast: false
|
|
73
|
-
}
|
|
74
|
-
} = opts
|
|
75
|
-
|
|
76
|
-
const params = [
|
|
77
|
-
'-o', filename,
|
|
78
|
-
'--fps', gifski.fps,
|
|
79
|
-
gifski.fast && '--fast',
|
|
80
|
-
'--quality', gifski.quality,
|
|
81
|
-
'-W', 600, // TODO: make this configurable
|
|
82
|
-
'--quiet'
|
|
83
|
-
]
|
|
84
|
-
.concat(frames)
|
|
85
|
-
.filter(Boolean)
|
|
86
|
-
|
|
87
|
-
const executable = process.env.GIFSKI_PATH || 'gifski'
|
|
88
|
-
const cmd = [ executable ].concat(params).join(' ')
|
|
89
|
-
if (opts.log) opts.log(cmd)
|
|
90
|
-
|
|
91
|
-
await execa.shell(cmd)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export default {
|
|
95
|
-
PARTIALS,
|
|
96
|
-
platform,
|
|
97
|
-
loadImage,
|
|
98
|
-
createImage,
|
|
99
|
-
saveImage,
|
|
100
|
-
saveGIF
|
|
101
|
-
}
|
package/lib/context.test.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import test from 'ava'
|
|
2
|
-
import fs from 'fs'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
import rmfr from 'rmfr'
|
|
5
|
-
import tempy from 'tempy'
|
|
6
|
-
|
|
7
|
-
import context from './context'
|
|
8
|
-
|
|
9
|
-
const fixtures = path.join(__dirname, '..', 'media')
|
|
10
|
-
|
|
11
|
-
test('monalisa.png', async (t) => {
|
|
12
|
-
const img0 = await context.loadImage(path.join(fixtures, 'monalisa.png'))
|
|
13
|
-
const temp = tempy.file({ extension: 'png' })
|
|
14
|
-
await context.saveImage(img0, temp)
|
|
15
|
-
const img1 = await context.loadImage(temp)
|
|
16
|
-
|
|
17
|
-
t.deepEqual(img0.data, img1.data)
|
|
18
|
-
await rmfr(temp)
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
test('flower.jpg', async (t) => {
|
|
22
|
-
const img0 = await context.loadImage(path.join(fixtures, 'flower.jpg'))
|
|
23
|
-
const temp = tempy.file({ extension: 'png' })
|
|
24
|
-
await context.saveImage(img0, temp)
|
|
25
|
-
const img1 = await context.loadImage(temp)
|
|
26
|
-
|
|
27
|
-
t.true(fs.existsSync(temp))
|
|
28
|
-
t.is(img0.width, img1.width)
|
|
29
|
-
t.is(img0.height, img1.height)
|
|
30
|
-
await rmfr(temp)
|
|
31
|
-
})
|
package/lib/core.js
DELETED
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
export const getMeanColor = (image) => {
|
|
4
|
-
const { data, width, height } = image
|
|
5
|
-
|
|
6
|
-
let r = 0
|
|
7
|
-
let g = 0
|
|
8
|
-
let b = 0
|
|
9
|
-
|
|
10
|
-
for (let i = 0; i < height; ++i) {
|
|
11
|
-
for (let j = 0; j < width; ++j) {
|
|
12
|
-
const o = (i * width + j) * 4
|
|
13
|
-
r += data[o + 0]
|
|
14
|
-
g += data[o + 1]
|
|
15
|
-
b += data[o + 2]
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
r = r / (width * height) | 0
|
|
20
|
-
g = g / (width * height) | 0
|
|
21
|
-
b = b / (width * height) | 0
|
|
22
|
-
|
|
23
|
-
return { r, g, b, a: 255 }
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const computeColor = (target, current, lines, alpha) => {
|
|
27
|
-
const { width } = target
|
|
28
|
-
const dataT = target.data
|
|
29
|
-
const dataC = current.data
|
|
30
|
-
|
|
31
|
-
const a = 255.0 / alpha
|
|
32
|
-
|
|
33
|
-
let count = 0
|
|
34
|
-
let r = 0.0
|
|
35
|
-
let g = 0.0
|
|
36
|
-
let b = 0.0
|
|
37
|
-
|
|
38
|
-
for (let i = 0; i < lines.length; ++i) {
|
|
39
|
-
const line = lines[i]
|
|
40
|
-
|
|
41
|
-
for (let j = line.x1; j <= line.x2; ++j) {
|
|
42
|
-
const o = (line.y * width + j) * 4
|
|
43
|
-
|
|
44
|
-
const tr = dataT[o + 0]
|
|
45
|
-
const tg = dataT[o + 1]
|
|
46
|
-
const tb = dataT[o + 2]
|
|
47
|
-
|
|
48
|
-
const cr = dataC[o + 0]
|
|
49
|
-
const cg = dataC[o + 1]
|
|
50
|
-
const cb = dataC[o + 2]
|
|
51
|
-
|
|
52
|
-
r += (tr - cr) * a + cr
|
|
53
|
-
g += (tg - cg) * a + cg
|
|
54
|
-
b += (tb - cb) * a + cb
|
|
55
|
-
|
|
56
|
-
++count
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (count > 0) {
|
|
61
|
-
r = Math.max(0, Math.min(255, (r / count))) | 0
|
|
62
|
-
g = Math.max(0, Math.min(255, (g / count))) | 0
|
|
63
|
-
b = Math.max(0, Math.min(255, (b / count))) | 0
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return { r, g, b, a: alpha }
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export const difference = (imageA, imageB) => {
|
|
70
|
-
const { width, height } = imageA
|
|
71
|
-
const dataA = imageA.data
|
|
72
|
-
const dataB = imageB.data
|
|
73
|
-
|
|
74
|
-
if (dataA.length !== dataB.length) {
|
|
75
|
-
throw new Error('image.difference incompatible images')
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
let sum = 0.0
|
|
79
|
-
|
|
80
|
-
for (let i = 0; i < height; ++i) {
|
|
81
|
-
for (let j = 0; j < width; ++j) {
|
|
82
|
-
const o = (i * width + j) * 4
|
|
83
|
-
|
|
84
|
-
const ar = dataA[o + 0]
|
|
85
|
-
const ag = dataA[o + 1]
|
|
86
|
-
const ab = dataA[o + 2]
|
|
87
|
-
|
|
88
|
-
const br = dataB[o + 0]
|
|
89
|
-
const bg = dataB[o + 1]
|
|
90
|
-
const bb = dataB[o + 2]
|
|
91
|
-
|
|
92
|
-
const dr = ar - br
|
|
93
|
-
const dg = ag - bg
|
|
94
|
-
const db = ab - bb
|
|
95
|
-
|
|
96
|
-
sum += (dr * dr + dg * dg + db * db)
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return Math.sqrt(sum / (width * height * 3)) / 255
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export const differencePartial = (target, before, after, score, lines) => {
|
|
104
|
-
const { width, height } = target
|
|
105
|
-
const dataT = target.data
|
|
106
|
-
const dataB = before.data
|
|
107
|
-
const dataA = after.data
|
|
108
|
-
|
|
109
|
-
if (dataT.length !== dataB.length || dataT.length !== dataA.length) {
|
|
110
|
-
throw new Error('image.differencePartial incompatible images')
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
let sum = Math.pow(score * 255, 2) * width * height * 3
|
|
114
|
-
|
|
115
|
-
for (let i = 0; i < lines.length; ++i) {
|
|
116
|
-
const line = lines[i]
|
|
117
|
-
|
|
118
|
-
for (let j = line.x1; j <= line.x2; ++j) {
|
|
119
|
-
const o = (line.y * width + j) * 4
|
|
120
|
-
|
|
121
|
-
const tr = dataT[o + 0]
|
|
122
|
-
const tg = dataT[o + 1]
|
|
123
|
-
const tb = dataT[o + 2]
|
|
124
|
-
|
|
125
|
-
const br = dataB[o + 0]
|
|
126
|
-
const bg = dataB[o + 1]
|
|
127
|
-
const bb = dataB[o + 2]
|
|
128
|
-
|
|
129
|
-
const ar = dataA[o + 0]
|
|
130
|
-
const ag = dataA[o + 1]
|
|
131
|
-
const ab = dataA[o + 2]
|
|
132
|
-
|
|
133
|
-
const dr1 = tr - br
|
|
134
|
-
const dg1 = tg - bg
|
|
135
|
-
const db1 = tb - bb
|
|
136
|
-
|
|
137
|
-
const dr2 = tr - ar
|
|
138
|
-
const dg2 = tg - ag
|
|
139
|
-
const db2 = tb - ab
|
|
140
|
-
|
|
141
|
-
sum -= (dr1 * dr1 + dg1 * dg1 + db1 * db1)
|
|
142
|
-
sum += (dr2 * dr2 + dg2 * dg2 + db2 * db2)
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return Math.sqrt(sum / (width * height * 3)) / 255
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export const copyLines = (dest, src, lines) => {
|
|
150
|
-
const { width, height } = src
|
|
151
|
-
const m = width * height * 4
|
|
152
|
-
|
|
153
|
-
if (dest.data.length !== src.data.length) {
|
|
154
|
-
throw new Error('image.copyLines incompatible images')
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
for (let i = 0; i < lines.length; ++i) {
|
|
158
|
-
const line = lines[i]
|
|
159
|
-
const o1 = Math.min(m, (line.y * width + line.x1) * 4)
|
|
160
|
-
const o2 = Math.min(m, (line.y * width + line.x2) * 4)
|
|
161
|
-
dest.data.set(src.data.slice(o1, o2), o1)
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export const drawLines = (image, color, lines) => {
|
|
166
|
-
const { data, width } = image
|
|
167
|
-
|
|
168
|
-
const sr = color.r
|
|
169
|
-
const sg = color.g
|
|
170
|
-
const sb = color.b
|
|
171
|
-
const sa = color.a
|
|
172
|
-
|
|
173
|
-
for (let i = 0; i < lines.length; ++i) {
|
|
174
|
-
const line = lines[i]
|
|
175
|
-
const ma = sa / 255
|
|
176
|
-
const a = 1.0 - ma
|
|
177
|
-
|
|
178
|
-
for (let j = line.x1; j <= line.x2; ++j) {
|
|
179
|
-
const o = (line.y * width + j) * 4
|
|
180
|
-
|
|
181
|
-
const dr = data[o + 0]
|
|
182
|
-
const dg = data[o + 1]
|
|
183
|
-
const db = data[o + 2]
|
|
184
|
-
|
|
185
|
-
data[o + 0] = (dr * a + sr * ma) | 0
|
|
186
|
-
data[o + 1] = (dg * a + sg * ma) | 0
|
|
187
|
-
data[o + 2] = (db * a + sb * ma) | 0
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
export default {
|
|
193
|
-
getMeanColor,
|
|
194
|
-
computeColor,
|
|
195
|
-
difference,
|
|
196
|
-
differencePartial,
|
|
197
|
-
copyLines,
|
|
198
|
-
drawLines
|
|
199
|
-
}
|
package/lib/core.test.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import test from 'ava'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
|
|
4
|
-
import context from './context'
|
|
5
|
-
import core from './core'
|
|
6
|
-
import Scanline from './scanline'
|
|
7
|
-
|
|
8
|
-
const fixtures = path.join(__dirname, '..', 'media')
|
|
9
|
-
|
|
10
|
-
test('difference', async (t) => {
|
|
11
|
-
const image = await context.loadImage(path.join(fixtures, 'monalisa.png'))
|
|
12
|
-
const color = core.getMeanColor(image)
|
|
13
|
-
const blank = context.createImage(image.width, image.height)
|
|
14
|
-
const current = context.createImage(image.width, image.height, color)
|
|
15
|
-
|
|
16
|
-
const diff0 = core.difference(image, blank)
|
|
17
|
-
t.true(diff0 > 0)
|
|
18
|
-
|
|
19
|
-
const diff1 = core.difference(image, current)
|
|
20
|
-
t.true(diff1 > 0)
|
|
21
|
-
t.true(diff0 > diff1)
|
|
22
|
-
|
|
23
|
-
const diff2 = core.difference(image, image)
|
|
24
|
-
t.is(diff2, 0)
|
|
25
|
-
|
|
26
|
-
let diff = diff0
|
|
27
|
-
for (let i = 0; i < image.height; ++i) {
|
|
28
|
-
const o = i * image.width * 4
|
|
29
|
-
blank.data.set(image.data.slice(o, o + image.width * 4), o)
|
|
30
|
-
const diff3 = core.difference(image, blank)
|
|
31
|
-
t.true(diff3 < diff)
|
|
32
|
-
diff = diff3
|
|
33
|
-
}
|
|
34
|
-
t.is(diff, 0)
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
test('drawLines', async (t) => {
|
|
38
|
-
const image = await context.loadImage(path.join(fixtures, 'monalisa.png'))
|
|
39
|
-
const color = core.getMeanColor(image)
|
|
40
|
-
const current = context.createImage(image.width, image.height, color)
|
|
41
|
-
|
|
42
|
-
const diff0 = core.difference(image, current)
|
|
43
|
-
t.true(diff0 > 0)
|
|
44
|
-
|
|
45
|
-
const lines = []
|
|
46
|
-
const c = { r: 255, g: 0, b: 0, a: 255 }
|
|
47
|
-
const m = image.width / 2 | 0
|
|
48
|
-
|
|
49
|
-
for (let i = 0; i < 16; ++i) {
|
|
50
|
-
lines.push(new Scanline(i, 5, m, 255))
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
t.is(lines.length, Scanline.filter(lines, image.width, image.height).length)
|
|
54
|
-
|
|
55
|
-
core.drawLines(current, c, lines)
|
|
56
|
-
|
|
57
|
-
for (let i = 0; i < 16; ++i) {
|
|
58
|
-
for (let j = 5; j < m; ++j) {
|
|
59
|
-
const o = (i * image.width + j) * 4
|
|
60
|
-
t.is(current.data[o + 0], 255)
|
|
61
|
-
t.is(current.data[o + 1], 0)
|
|
62
|
-
t.is(current.data[o + 2], 0)
|
|
63
|
-
t.is(current.data[o + 3], 255)
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const diff1 = core.difference(image, current)
|
|
68
|
-
t.true(diff1 > 0)
|
|
69
|
-
t.true(diff1 > diff0)
|
|
70
|
-
})
|
package/lib/model.js
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
import * as chromatism from 'chromatism'
|
|
2
|
-
import ow from 'ow'
|
|
3
|
-
|
|
4
|
-
import core from './core'
|
|
5
|
-
import optimize from './optimize'
|
|
6
|
-
import Worker from './worker'
|
|
7
|
-
|
|
8
|
-
export default class Model {
|
|
9
|
-
constructor (opts) {
|
|
10
|
-
const {
|
|
11
|
-
context,
|
|
12
|
-
target,
|
|
13
|
-
backgroundColor,
|
|
14
|
-
outputSize,
|
|
15
|
-
numCandidates = 1
|
|
16
|
-
} = opts
|
|
17
|
-
|
|
18
|
-
const { width, height } = target
|
|
19
|
-
const aspect = width / height
|
|
20
|
-
|
|
21
|
-
if (outputSize) {
|
|
22
|
-
if (aspect >= 1) {
|
|
23
|
-
this.sw = outputSize
|
|
24
|
-
this.sh = outputSize / aspect | 0
|
|
25
|
-
this.scale = outputSize / width
|
|
26
|
-
} else {
|
|
27
|
-
this.sw = outputSize * aspect | 0
|
|
28
|
-
this.sh = outputSize
|
|
29
|
-
this.scale = outputSize / height
|
|
30
|
-
}
|
|
31
|
-
} else {
|
|
32
|
-
this.sw = width
|
|
33
|
-
this.sh = height
|
|
34
|
-
this.scale = 1
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
this.context = context
|
|
38
|
-
this.target = target
|
|
39
|
-
this.backgroundColor = backgroundColor
|
|
40
|
-
|
|
41
|
-
this.current = this.createImage()
|
|
42
|
-
this.score = core.difference(this.target, this.current)
|
|
43
|
-
|
|
44
|
-
this.shapes = []
|
|
45
|
-
this.colors = []
|
|
46
|
-
this.scores = []
|
|
47
|
-
this.workers = []
|
|
48
|
-
|
|
49
|
-
for (let i = 0; i < numCandidates; ++i) {
|
|
50
|
-
this.workers.push(new Worker({
|
|
51
|
-
context,
|
|
52
|
-
target
|
|
53
|
-
}))
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (this.context.PARTIALS) {
|
|
57
|
-
this.before = this.context.createImage(width, height)
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
createImage () {
|
|
62
|
-
const { width, height } = this.target
|
|
63
|
-
return this.context.createImage(width, height, this.backgroundColor)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
add (shape, alpha) {
|
|
67
|
-
const lines = shape.rasterize()
|
|
68
|
-
const color = core.computeColor(this.target, this.current, lines, alpha)
|
|
69
|
-
let score
|
|
70
|
-
|
|
71
|
-
if (this.context.PARTIALS) {
|
|
72
|
-
this.before.data.set(this.current.data)
|
|
73
|
-
core.drawLines(this.current, color, lines)
|
|
74
|
-
score = core.differencePartial(this.target, this.before, this.current, this.score, lines)
|
|
75
|
-
} else {
|
|
76
|
-
core.drawLines(this.current, color, lines)
|
|
77
|
-
score = core.difference(this.target, this.current)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
this.score = score
|
|
81
|
-
this.shapes.push(shape)
|
|
82
|
-
this.colors.push(color)
|
|
83
|
-
this.scores.push(score)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
step (opts) {
|
|
87
|
-
ow(opts, ow.object.plain)
|
|
88
|
-
ow(opts.shapeType, ow.string.nonEmpty)
|
|
89
|
-
ow(opts.shapeAlpha, ow.number.integer.positive)
|
|
90
|
-
ow(opts.numCandidateShapes, ow.number.integer.positive)
|
|
91
|
-
ow(opts.numCandidateMutations, ow.number.integer.positive)
|
|
92
|
-
|
|
93
|
-
let state = this._getBestCandidateState(opts)
|
|
94
|
-
this.add(state.shape, state.alpha)
|
|
95
|
-
|
|
96
|
-
if (opts.numCandidateExtras) {
|
|
97
|
-
ow(opts.numCandidateExtras, ow.number.integer)
|
|
98
|
-
|
|
99
|
-
for (let i = 0; i < opts.numCandidateExtras; ++i) {
|
|
100
|
-
state.worker.init(this.current, this.score)
|
|
101
|
-
const a = state.energy()
|
|
102
|
-
state = optimize.hillClimb(state, opts.numCandidateMutations)
|
|
103
|
-
const b = state.energy()
|
|
104
|
-
if (b <= a) break
|
|
105
|
-
|
|
106
|
-
this.add(state.shape, state.alpha)
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return this.workers
|
|
111
|
-
.reduce((sum, worker) => sum + worker.counter, 0)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
_getBestCandidateState (opts) {
|
|
115
|
-
const states = this.workers
|
|
116
|
-
.map((worker) => {
|
|
117
|
-
worker.init(this.current, this.score)
|
|
118
|
-
return optimize.getBestHillClimbState(worker, opts)
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
let bestEnergy = null
|
|
122
|
-
let bestState = null
|
|
123
|
-
|
|
124
|
-
for (let i = 0; i < states.length; ++i) {
|
|
125
|
-
const state = states[i]
|
|
126
|
-
const energy = state.energy()
|
|
127
|
-
|
|
128
|
-
if (!i || energy < bestEnergy) {
|
|
129
|
-
bestEnergy = energy
|
|
130
|
-
bestState = state
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return bestState
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
toSVG () {
|
|
138
|
-
const bg = chromatism.convert(this.backgroundColor).hex
|
|
139
|
-
const body = this.shapes
|
|
140
|
-
.map((shape, index) => {
|
|
141
|
-
const color = this.colors[index]
|
|
142
|
-
const fill = chromatism.convert(color).hex
|
|
143
|
-
const attrs = `fill="${fill}" fill-opacity="${color.a / 255}"`
|
|
144
|
-
return shape.toSVG(attrs)
|
|
145
|
-
})
|
|
146
|
-
.join('\n')
|
|
147
|
-
|
|
148
|
-
return `
|
|
149
|
-
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="${this.sw}" height="${this.sh}">
|
|
150
|
-
<rect x="0" y="0" width="${this.sw}" height="${this.sh}" fill="${bg}" />
|
|
151
|
-
<g transform="scale(${this.scale}) translate(0.5 0.5)">
|
|
152
|
-
${body}
|
|
153
|
-
</g>
|
|
154
|
-
</svg>
|
|
155
|
-
`
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
toFrames (scoreDelta = 0) {
|
|
159
|
-
throw new Error('TODO')
|
|
160
|
-
|
|
161
|
-
/*
|
|
162
|
-
for (let i = 0; i < this.shapes.length; ++i) {
|
|
163
|
-
const shape = this.shapes[i]
|
|
164
|
-
const color = this.colors[i]
|
|
165
|
-
}
|
|
166
|
-
*/
|
|
167
|
-
}
|
|
168
|
-
}
|