etro 0.6.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/.env +2 -0
- package/.github/workflows/nodejs.yml +27 -0
- package/CHANGELOG.md +109 -0
- package/CODE_OF_CONDUCT.md +77 -0
- package/CONTRIBUTING.md +155 -0
- package/LICENSE +674 -0
- package/README.md +57 -0
- package/dist/etro.js +3397 -0
- package/docs/effect.js.html +1215 -0
- package/docs/event.js.html +145 -0
- package/docs/index.html +81 -0
- package/docs/index.js.html +92 -0
- package/docs/layer.js.html +888 -0
- package/docs/module-effect-GaussianBlurComponent.html +345 -0
- package/docs/module-effect.Brightness.html +339 -0
- package/docs/module-effect.Channels.html +319 -0
- package/docs/module-effect.ChromaKey.html +611 -0
- package/docs/module-effect.Contrast.html +339 -0
- package/docs/module-effect.EllipticalMask.html +200 -0
- package/docs/module-effect.GaussianBlur.html +202 -0
- package/docs/module-effect.GaussianBlurHorizontal.html +242 -0
- package/docs/module-effect.GaussianBlurVertical.html +242 -0
- package/docs/module-effect.Pixelate.html +330 -0
- package/docs/module-effect.Shader.html +1227 -0
- package/docs/module-effect.Stack.html +406 -0
- package/docs/module-effect.Transform.Matrix.html +193 -0
- package/docs/module-effect.Transform.html +1174 -0
- package/docs/module-effect.html +148 -0
- package/docs/module-event.html +473 -0
- package/docs/module-index.html +186 -0
- package/docs/module-layer-Media.html +1116 -0
- package/docs/module-layer-MediaMixin.html +164 -0
- package/docs/module-layer.Audio.html +1188 -0
- package/docs/module-layer.Base.html +629 -0
- package/docs/module-layer.Image.html +1421 -0
- package/docs/module-layer.Text.html +1731 -0
- package/docs/module-layer.Video.html +1938 -0
- package/docs/module-layer.Visual.html +1698 -0
- package/docs/module-layer.html +137 -0
- package/docs/module-movie.html +3118 -0
- package/docs/module-util.Color.html +702 -0
- package/docs/module-util.Font.html +395 -0
- package/docs/module-util.html +845 -0
- package/docs/movie.js.html +689 -0
- package/docs/scripts/collapse.js +20 -0
- package/docs/scripts/linenumber.js +25 -0
- package/docs/scripts/nav.js +12 -0
- package/docs/scripts/polyfill.js +4 -0
- package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
- package/docs/scripts/prettify/lang-css.js +2 -0
- package/docs/scripts/prettify/prettify.js +28 -0
- package/docs/scripts/search.js +83 -0
- package/docs/styles/jsdoc.css +671 -0
- package/docs/styles/prettify.css +79 -0
- package/docs/util.js.html +503 -0
- package/eslint.conf.js +28 -0
- package/eslint.test-conf.js +4 -0
- package/examples/application/readme-screenshot.html +86 -0
- package/examples/application/video-player.html +131 -0
- package/examples/application/webcam.html +28 -0
- package/examples/introduction/audio.html +52 -0
- package/examples/introduction/effects.html +56 -0
- package/examples/introduction/export.html +70 -0
- package/examples/introduction/functions.html +35 -0
- package/examples/introduction/hello-world1.html +33 -0
- package/examples/introduction/hello-world2.html +32 -0
- package/examples/introduction/keyframes.html +67 -0
- package/examples/introduction/media.html +55 -0
- package/examples/introduction/text.html +27 -0
- package/jsdoc.conf.json +3 -0
- package/karma.conf.js +60 -0
- package/package.json +63 -0
- package/private-todo.txt +70 -0
- package/rename-file.sh +18 -0
- package/rename-versions.sh +14 -0
- package/rename.sh +22 -0
- package/rollup.config.js +31 -0
- package/screenshots/2019-08-17_0.png +0 -0
- package/scripts/gen-effect-samples.html +99 -0
- package/scripts/save-effect-samples.js +43 -0
- package/spec/assets/effect/gaussian-blur-horizontal.png +0 -0
- package/spec/assets/effect/gaussian-blur-vertical.png +0 -0
- package/spec/assets/effect/original.png +0 -0
- package/spec/assets/effect/pixelate.png +0 -0
- package/spec/assets/effect/transform/multiply.png +0 -0
- package/spec/assets/effect/transform/rotate.png +0 -0
- package/spec/assets/effect/transform/scale-fraction.png +0 -0
- package/spec/assets/effect/transform/scale.png +0 -0
- package/spec/assets/effect/transform/translate-fraction.png +0 -0
- package/spec/assets/effect/transform/translate.png +0 -0
- package/spec/assets/layer/audio.wav +0 -0
- package/spec/assets/layer/image.jpg +0 -0
- package/spec/effect.spec.js +352 -0
- package/spec/event.spec.js +25 -0
- package/spec/layer.spec.js +128 -0
- package/spec/movie.spec.js +154 -0
- package/spec/util.spec.js +285 -0
- package/src/effect.js +1265 -0
- package/src/event.js +78 -0
- package/src/index.js +23 -0
- package/src/layer.js +875 -0
- package/src/movie.js +636 -0
- package/src/util.js +487 -0
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>util.js - Documentation</title>
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
<script src="scripts/prettify/prettify.js"></script>
|
|
10
|
+
<script src="scripts/prettify/lang-css.js"></script>
|
|
11
|
+
<!--[if lt IE 9]>
|
|
12
|
+
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
|
13
|
+
<![endif]-->
|
|
14
|
+
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
|
|
15
|
+
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
|
|
16
|
+
<script src="scripts/nav.js" defer></script>
|
|
17
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
18
|
+
</head>
|
|
19
|
+
<body>
|
|
20
|
+
|
|
21
|
+
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
|
|
22
|
+
<label for="nav-trigger" class="navicon-button x">
|
|
23
|
+
<div class="navicon"></div>
|
|
24
|
+
</label>
|
|
25
|
+
|
|
26
|
+
<label for="nav-trigger" class="overlay"></label>
|
|
27
|
+
|
|
28
|
+
<nav >
|
|
29
|
+
|
|
30
|
+
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="module-effect.Brightness.html">Brightness</a></li><li><a href="module-effect.Channels.html">Channels</a></li><li><a href="module-effect.ChromaKey.html">ChromaKey</a></li><li><a href="module-effect.Contrast.html">Contrast</a></li><li><a href="module-effect.EllipticalMask.html">EllipticalMask</a></li><li><a href="module-effect.GaussianBlur.html">GaussianBlur</a></li><li><a href="module-effect.GaussianBlurHorizontal.html">GaussianBlurHorizontal</a></li><li><a href="module-effect.GaussianBlurVertical.html">GaussianBlurVertical</a></li><li><a href="module-effect.Pixelate.html">Pixelate</a></li><li><a href="module-effect.Shader.html">Shader</a><ul class='methods'><li data-type='method'><a href="module-effect.Shader.html#._initBuffer">_initBuffer</a></li><li data-type='method'><a href="module-effect.Shader.html#._loadTexture">_loadTexture</a></li><li data-type='method'><a href="module-effect.Shader.html#_prepareValue">_prepareValue</a></li></ul></li><li><a href="module-effect.Stack.html">Stack</a><ul class='methods'><li data-type='method'><a href="module-effect.Stack.html#addEffect">addEffect</a></li></ul></li><li><a href="module-effect.Transform.html">Transform</a><ul class='methods'><li data-type='method'><a href="module-effect.Transform.html#.Matrix#cell">Matrix#cell</a></li><li data-type='method'><a href="module-effect.Transform.html#.Matrix#multiply">Matrix#multiply</a></li><li data-type='method'><a href="module-effect.Transform.html#.Matrix#rotate">Matrix#rotate</a></li><li data-type='method'><a href="module-effect.Transform.html#.Matrix#scale">Matrix#scale</a></li><li data-type='method'><a href="module-effect.Transform.html#.Matrix#translate">Matrix#translate</a></li></ul></li><li><a href="module-effect.Transform.Matrix.html">Matrix</a></li><li><a href="module-effect-GaussianBlurComponent.html">GaussianBlurComponent</a></li><li><a href="module-layer.Audio.html">Audio</a></li><li><a href="module-layer.Base.html">Base</a><ul class='methods'><li data-type='method'><a href="module-layer.Base.html#_render">_render</a></li></ul></li><li><a href="module-layer.Image.html">Image</a></li><li><a href="module-layer.Text.html">Text</a></li><li><a href="module-layer.Video.html">Video</a></li><li><a href="module-layer.Visual.html">Visual</a><ul class='methods'><li data-type='method'><a href="module-layer.Visual.html#_render">_render</a></li><li data-type='method'><a href="module-layer.Visual.html#addEffect">addEffect</a></li></ul></li><li><a href="module-layer-Media.html">Media</a></li><li><a href="module-movie.html">movie</a><ul class='methods'><li data-type='method'><a href="module-movie.html#addEffect">addEffect</a></li><li data-type='method'><a href="module-movie.html#addLayer">addLayer</a></li><li data-type='method'><a href="module-movie.html#pause">pause</a></li><li data-type='method'><a href="module-movie.html#play">play</a></li><li data-type='method'><a href="module-movie.html#publishToLayers">publishToLayers</a></li><li data-type='method'><a href="module-movie.html#record">record</a></li><li data-type='method'><a href="module-movie.html#refresh">refresh</a></li><li data-type='method'><a href="module-movie.html#setCurrentTime">setCurrentTime</a></li><li data-type='method'><a href="module-movie.html#stop">stop</a></li></ul></li><li><a href="module-util.Color.html">Color</a><ul class='methods'><li data-type='method'><a href="module-util.Color.html#toString">toString</a></li></ul></li><li><a href="module-util.Font.html">Font</a><ul class='methods'><li data-type='method'><a href="module-util.Font.html#toString">toString</a></li></ul></li></ul><h3>Modules</h3><ul><li><a href="module-effect.html">effect</a></li><li><a href="module-event.html">event</a><ul class='methods'><li data-type='method'><a href="module-event.html#.publish">publish</a></li><li data-type='method'><a href="module-event.html#.subscribe">subscribe</a></li></ul></li><li><a href="module-index.html">index</a></li><li><a href="module-layer.html">layer</a></li><li><a href="module-movie.html">movie</a><ul class='methods'><li data-type='method'><a href="module-movie.html#addEffect">addEffect</a></li><li data-type='method'><a href="module-movie.html#addLayer">addLayer</a></li><li data-type='method'><a href="module-movie.html#pause">pause</a></li><li data-type='method'><a href="module-movie.html#play">play</a></li><li data-type='method'><a href="module-movie.html#publishToLayers">publishToLayers</a></li><li data-type='method'><a href="module-movie.html#record">record</a></li><li data-type='method'><a href="module-movie.html#refresh">refresh</a></li><li data-type='method'><a href="module-movie.html#setCurrentTime">setCurrentTime</a></li><li data-type='method'><a href="module-movie.html#stop">stop</a></li></ul></li><li><a href="module-util.html">util</a><ul class='methods'><li data-type='method'><a href="module-util.html#.parseColor">parseColor</a></li><li data-type='method'><a href="module-util.html#.parseFont">parseFont</a></li><li data-type='method'><a href="module-util.html#.watchPublic">watchPublic</a></li><li data-type='method'><a href="module-util.html#~isKeyFrames">isKeyFrames</a></li></ul></li></ul><h3>Mixins</h3><ul><li><a href="module-layer-MediaMixin.html">MediaMixin</a></li></ul>
|
|
31
|
+
</nav>
|
|
32
|
+
|
|
33
|
+
<div id="main">
|
|
34
|
+
|
|
35
|
+
<h1 class="page-title">util.js</h1>
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
<section>
|
|
44
|
+
<article>
|
|
45
|
+
<pre class="prettyprint source linenums"><code>/**
|
|
46
|
+
* @module util
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
import { publish } from './event.js'
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Merges `options` with `defaultOptions`, and then copies the properties with the keys in `defaultOptions`
|
|
53
|
+
* from the merged object to `destObj`.
|
|
54
|
+
*
|
|
55
|
+
* @return {undefined}
|
|
56
|
+
* @todo Make methods like getDefaultOptions private
|
|
57
|
+
*/
|
|
58
|
+
export function applyOptions (options, destObj) {
|
|
59
|
+
const defaultOptions = destObj.getDefaultOptions()
|
|
60
|
+
|
|
61
|
+
// validate; make sure `keys` doesn't have any extraneous items
|
|
62
|
+
for (const option in options) {
|
|
63
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
64
|
+
if (!defaultOptions.hasOwnProperty(option)) {
|
|
65
|
+
throw new Error("Invalid option: '" + option + "'")
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// merge options and defaultOptions
|
|
70
|
+
options = { ...defaultOptions, ...options }
|
|
71
|
+
|
|
72
|
+
// copy options
|
|
73
|
+
for (const option in options) {
|
|
74
|
+
if (!(option in destObj)) {
|
|
75
|
+
destObj[option] = options[option]
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// https://stackoverflow.com/a/8024294/3783155
|
|
81
|
+
/**
|
|
82
|
+
* Get all inherited keys
|
|
83
|
+
* @param {object} obj
|
|
84
|
+
* @param {boolean} excludeObjectClass - don't add properties of the <code>Object</code> prototype
|
|
85
|
+
* @private
|
|
86
|
+
*/
|
|
87
|
+
function getAllPropertyNames (obj, excludeObjectClass) {
|
|
88
|
+
let props = []
|
|
89
|
+
do {
|
|
90
|
+
props = props.concat(Object.getOwnPropertyNames(obj))
|
|
91
|
+
} while ((obj = Object.getPrototypeOf(obj)) && (excludeObjectClass ? obj.constructor.name !== 'Object' : true))
|
|
92
|
+
return props
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @return {boolean} <code>true</code> if <code>property</code> is a non-array object and all of its own
|
|
97
|
+
* property keys are numbers or <code>"interpolate"</code> or <code>"interpolationKeys"</code>, and
|
|
98
|
+
* <code>false</code> otherwise.
|
|
99
|
+
*/
|
|
100
|
+
function isKeyFrames (property) {
|
|
101
|
+
if ((typeof property !== 'object' || property === null) || Array.isArray(property)) {
|
|
102
|
+
return false
|
|
103
|
+
}
|
|
104
|
+
// is reduce slow? I think it is
|
|
105
|
+
// let keys = Object.keys(property); // own propeties
|
|
106
|
+
const keys = getAllPropertyNames(property, true) // includes non-enumerable properties (except that of `Object`)
|
|
107
|
+
for (let i = 0; i < keys.length; i++) {
|
|
108
|
+
const key = keys[i]
|
|
109
|
+
// convert key to number, because object keys are always converted to strings
|
|
110
|
+
if (isNaN(key) && !(key === 'interpolate' || key === 'interpolationKeys')) {
|
|
111
|
+
return false
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// If it's an empty object, don't treat is as keyframe set.
|
|
115
|
+
// https://stackoverflow.com/a/32108184/3783155
|
|
116
|
+
const isEmpty = property.constructor === Object && Object.entries(property).length === 0
|
|
117
|
+
return !isEmpty
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Calculates the value of keyframe set <code>property</code> at <code>time</code> if
|
|
122
|
+
* <code>property</code> is an array, or returns <code>property</code>, assuming that it's a number.
|
|
123
|
+
*
|
|
124
|
+
* @param {(*|module:util.KeyFrames)} property - value or map of time-to-value pairs for keyframes
|
|
125
|
+
* @param {object} element - the object to which the property belongs
|
|
126
|
+
* @param {number} time - time to calculate keyframes for, if necessary
|
|
127
|
+
*
|
|
128
|
+
* Note that only values used in keyframes that numbers or objects (including arrays) are interpolated.
|
|
129
|
+
* All other values are taken sequentially with no interpolation. JavaScript will convert parsed colors,
|
|
130
|
+
* if created correctly, to their string representations when assigned to a CanvasRenderingContext2D property
|
|
131
|
+
* (I'm pretty sure).
|
|
132
|
+
*
|
|
133
|
+
* @todo Is this function efficient?
|
|
134
|
+
* @todo Update doc @params to allow for keyframes
|
|
135
|
+
*
|
|
136
|
+
* @typedef {Object} module:util.KeyFrames
|
|
137
|
+
* @property {function} interpolate - the function to interpolate between keyframes, defaults to
|
|
138
|
+
* {@link module:util.linearInterp}
|
|
139
|
+
* @property {string[]} interpolationKeys - keys to interpolate for objects, defaults to all
|
|
140
|
+
* own enumerable properties
|
|
141
|
+
*/
|
|
142
|
+
export function val (property, element, time) {
|
|
143
|
+
if (isKeyFrames(property)) {
|
|
144
|
+
// if (Object.keys(property).length === 0) throw "Empty key frame set"; // this will never be executed
|
|
145
|
+
if (time === undefined) {
|
|
146
|
+
throw new Error('|time| is undefined or null')
|
|
147
|
+
}
|
|
148
|
+
// I think .reduce and such are slow to do per-frame (or more)?
|
|
149
|
+
// lower is the max beneath time, upper is the min above time
|
|
150
|
+
let lowerTime = 0; let upperTime = Infinity
|
|
151
|
+
let lowerValue = null; let upperValue = null // default values for the inequalities
|
|
152
|
+
for (let keyTime in property) {
|
|
153
|
+
const keyValue = property[keyTime]
|
|
154
|
+
keyTime = +keyTime // valueOf to convert to number
|
|
155
|
+
|
|
156
|
+
if (lowerTime <= keyTime && keyTime <= time) {
|
|
157
|
+
lowerValue = keyValue
|
|
158
|
+
lowerTime = keyTime
|
|
159
|
+
}
|
|
160
|
+
if (time <= keyTime && keyTime <= upperTime) {
|
|
161
|
+
upperValue = keyValue
|
|
162
|
+
upperTime = keyTime
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// TODO: support custom interpolation for 'other' types
|
|
166
|
+
if (lowerValue === null) {
|
|
167
|
+
throw new Error(`No keyframes located before or at time ${time}.`)
|
|
168
|
+
}
|
|
169
|
+
// no need for upperValue if it is flat interpolation
|
|
170
|
+
if (!(typeof lowerValue === 'number' || typeof lowerValue === 'object')) {
|
|
171
|
+
return lowerValue
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (upperValue === null) {
|
|
175
|
+
throw new Error(`No keyframes located after or at time ${time}.`)
|
|
176
|
+
}
|
|
177
|
+
if (typeof lowerValue !== typeof upperValue) {
|
|
178
|
+
throw new Error('Type mismatch in keyframe values')
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// interpolate
|
|
182
|
+
// the following should mean that there is a key frame *at* |time|; prevents division by zero below
|
|
183
|
+
if (upperTime === lowerTime) {
|
|
184
|
+
return upperValue
|
|
185
|
+
}
|
|
186
|
+
const progress = time - lowerTime; const percentProgress = progress / (upperTime - lowerTime)
|
|
187
|
+
const interpolate = property.interpolate || linearInterp
|
|
188
|
+
return interpolate(lowerValue, upperValue, percentProgress, property.interpolationKeys)
|
|
189
|
+
} else if (typeof property === 'function') {
|
|
190
|
+
return property(element, time) // TODO? add more args
|
|
191
|
+
} else {
|
|
192
|
+
return property // "primitive" value
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* export function floorInterp(x1, x2, t, objectKeys) {
|
|
197
|
+
// https://stackoverflow.com/a/25835337/3783155 (TODO: preserve getters/setters, etc?)
|
|
198
|
+
return !objectKeys ? x1 : objectKeys.reduce((a, x) => {
|
|
199
|
+
if (x1.hasOwnProperty(x)) a[x] = o[x]; // ignore x2
|
|
200
|
+
return a;
|
|
201
|
+
}, Object.create(Object.getPrototypeOf(x1)));
|
|
202
|
+
} */
|
|
203
|
+
|
|
204
|
+
export function linearInterp (x1, x2, t, objectKeys) {
|
|
205
|
+
if (typeof x1 !== typeof x2) {
|
|
206
|
+
throw new Error('Type mismatch')
|
|
207
|
+
}
|
|
208
|
+
if (typeof x1 !== 'number' && typeof x1 !== 'object') {
|
|
209
|
+
return x1
|
|
210
|
+
} // flat interpolation (floor)
|
|
211
|
+
if (typeof x1 === 'object') { // to work with objects (including arrays)
|
|
212
|
+
// TODO: make this code DRY
|
|
213
|
+
if (Object.getPrototypeOf(x1) !== Object.getPrototypeOf(x2)) {
|
|
214
|
+
throw new Error('Prototype mismatch')
|
|
215
|
+
}
|
|
216
|
+
const int = Object.create(Object.getPrototypeOf(x1)) // preserve prototype of objects
|
|
217
|
+
// only take the union of properties
|
|
218
|
+
const keys = Object.keys(x1) || objectKeys
|
|
219
|
+
for (let i = 0; i < keys.length; i++) {
|
|
220
|
+
const key = keys[i]
|
|
221
|
+
// (only take the union of properties)
|
|
222
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
223
|
+
if (!x1.hasOwnProperty(key) || !x2.hasOwnProperty(key)) {
|
|
224
|
+
continue
|
|
225
|
+
}
|
|
226
|
+
int[key] = linearInterp(x1[key], x2[key], t)
|
|
227
|
+
}
|
|
228
|
+
return int
|
|
229
|
+
}
|
|
230
|
+
return (1 - t) * x1 + t * x2
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function cosineInterp (x1, x2, t, objectKeys) {
|
|
234
|
+
if (typeof x1 !== typeof x2) {
|
|
235
|
+
throw new Error('Type mismatch')
|
|
236
|
+
}
|
|
237
|
+
if (typeof x1 !== 'number' && typeof x1 !== 'object') {
|
|
238
|
+
return x1
|
|
239
|
+
} // flat interpolation (floor)
|
|
240
|
+
if (typeof x1 === 'object' && typeof x2 === 'object') { // to work with objects (including arrays)
|
|
241
|
+
if (Object.getPrototypeOf(x1) !== Object.getPrototypeOf(x2)) {
|
|
242
|
+
throw new Error('Prototype mismatch')
|
|
243
|
+
}
|
|
244
|
+
const int = Object.create(Object.getPrototypeOf(x1)) // preserve prototype of objects
|
|
245
|
+
// only take the union of properties
|
|
246
|
+
const keys = Object.keys(x1) || objectKeys
|
|
247
|
+
for (let i = 0; i < keys.length; i++) {
|
|
248
|
+
const key = keys[i]
|
|
249
|
+
// (only take the union of properties)
|
|
250
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
251
|
+
if (!x1.hasOwnProperty(key) || !x2.hasOwnProperty(key)) {
|
|
252
|
+
continue
|
|
253
|
+
}
|
|
254
|
+
int[key] = cosineInterp(x1[key], x2[key], t)
|
|
255
|
+
}
|
|
256
|
+
return int
|
|
257
|
+
}
|
|
258
|
+
const cos = Math.cos(Math.PI / 2 * t)
|
|
259
|
+
return cos * x1 + (1 - cos) * x2
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* An rgba color, for proper interpolation and shader effects
|
|
264
|
+
*/
|
|
265
|
+
export class Color {
|
|
266
|
+
/**
|
|
267
|
+
* @param {number} r
|
|
268
|
+
* @param {number} g
|
|
269
|
+
* @param {number} b
|
|
270
|
+
* @param {number} a
|
|
271
|
+
*/
|
|
272
|
+
constructor (r, g, b, a = 255) {
|
|
273
|
+
/** @type number */
|
|
274
|
+
this.r = r
|
|
275
|
+
/** @type number */
|
|
276
|
+
this.g = g
|
|
277
|
+
/** @type number */
|
|
278
|
+
this.b = b
|
|
279
|
+
/** @type number */
|
|
280
|
+
this.a = a
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Converts to css color
|
|
285
|
+
*/
|
|
286
|
+
toString () {
|
|
287
|
+
return `rgba(${this.r}, ${this.g}, ${this.b}, ${this.a})`
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// https://stackoverflow.com/a/19366389/3783155
|
|
292
|
+
function memoize (factory, ctx) {
|
|
293
|
+
const cache = {}
|
|
294
|
+
return key => {
|
|
295
|
+
if (!(key in cache)) {
|
|
296
|
+
cache[key] = factory.call(ctx, key)
|
|
297
|
+
}
|
|
298
|
+
return cache[key]
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
const parseColorCanvas = document.createElement('parseColorCanvas')
|
|
302
|
+
parseColorCanvas.width = parseColorCanvas.height = 1
|
|
303
|
+
const parseColorCtx = parseColorCanvas.getContext('2d')
|
|
304
|
+
/**
|
|
305
|
+
* Converts a css color string to a {@link module:util.Color} object representation.
|
|
306
|
+
* @param {string} str
|
|
307
|
+
* @return {module:util.Color} the parsed color
|
|
308
|
+
*/
|
|
309
|
+
export function parseColor (str) {
|
|
310
|
+
// TODO - find a better way to cope with the fact that invalid
|
|
311
|
+
// values of "col" are ignored
|
|
312
|
+
parseColorCtx.clearRect(0, 0, 1, 1)
|
|
313
|
+
parseColorCtx.fillStyle = str
|
|
314
|
+
parseColorCtx.fillRect(0, 0, 1, 1)
|
|
315
|
+
return new Color(...parseColorCtx.getImageData(0, 0, 1, 1).data)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* A font, for proper interpolation
|
|
320
|
+
*/
|
|
321
|
+
export class Font {
|
|
322
|
+
/**
|
|
323
|
+
* @param {number} size
|
|
324
|
+
* @param {string} family
|
|
325
|
+
* @param {string} sizeUnit
|
|
326
|
+
*/
|
|
327
|
+
constructor (size, family, sizeUnit = 'px') {
|
|
328
|
+
this.size = size
|
|
329
|
+
this.family = family
|
|
330
|
+
this.sizeUnit = sizeUnit
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Converts to a css font
|
|
335
|
+
*/
|
|
336
|
+
toString () {
|
|
337
|
+
return `${this.size}${this.sizeUnit} ${this.family}`
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Converts a css font string to a {@link module:util.Font} object representation.
|
|
343
|
+
* @param {string} str
|
|
344
|
+
* @return {module:util.Font} the parsed font
|
|
345
|
+
*/
|
|
346
|
+
export function parseFont (str) {
|
|
347
|
+
const split = str.split(' ')
|
|
348
|
+
if (split.length !== 2) {
|
|
349
|
+
throw new Error(`Invalid font '${str}'`)
|
|
350
|
+
}
|
|
351
|
+
const sizeWithUnit = split[0]; const family = split[1]
|
|
352
|
+
const size = parseFloat(sizeWithUnit); const sizeUnit = sizeWithUnit.substring(size.toString().length)
|
|
353
|
+
return new Font(size, family, sizeUnit)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/*
|
|
357
|
+
* Attempts to solve the diamond inheritance problem using mixins
|
|
358
|
+
* See {@link http://javascriptweblog.wordpress.com/2011/05/31/a-fresh-look-at-javascript-mixins/}<br>
|
|
359
|
+
*
|
|
360
|
+
* <strong>Note that the caller has to explicitly update the class value and as well as the class's property
|
|
361
|
+
* <code>constructor</code> to its prototype's constructor.</strong><br>
|
|
362
|
+
*
|
|
363
|
+
* This throws an error when composing functions with return values; unless if the composed function is a
|
|
364
|
+
* constructor, which is handled specially.
|
|
365
|
+
*
|
|
366
|
+
* Note that all properties must be functions for this to work as expected.
|
|
367
|
+
*
|
|
368
|
+
* If the destination and source have the methods with the same name (key), assign a new function
|
|
369
|
+
* that calls both with the given arguments. The arguments list passed to each subfunction will be the
|
|
370
|
+
* argument list that was called to the composite function.
|
|
371
|
+
*
|
|
372
|
+
* This function only works with functions, getters and setters.
|
|
373
|
+
*
|
|
374
|
+
* TODO: make a lot more robust
|
|
375
|
+
* TODO: rethink my ways... this is evil
|
|
376
|
+
*/
|
|
377
|
+
/* export function extendProto(destination, source) {
|
|
378
|
+
for (let name in source) {
|
|
379
|
+
const extendMethod = (sourceDescriptor, which) => {
|
|
380
|
+
let sourceFn = sourceDescriptor[which],
|
|
381
|
+
origDestDescriptor = Object.getOwnPropertyDescriptor(destination, name),
|
|
382
|
+
origDestFn = origDestDescriptor ? origDestDescriptor[which] : undefined;
|
|
383
|
+
let destFn = !origDestFn ? sourceFn : function compositeMethod() { // `function` or `()` ?
|
|
384
|
+
try {
|
|
385
|
+
// |.apply()| because we're seperating the method from the object, so return the value
|
|
386
|
+
// of |this| back to the function
|
|
387
|
+
let r1 = origDestFn.apply(this, arguments),
|
|
388
|
+
r2 = sourceFn.apply(this, arguments);
|
|
389
|
+
if (r1 || r2) throw "Return value in composite method"; // null will slip by ig
|
|
390
|
+
} catch (e) {
|
|
391
|
+
if (e.toString() === "TypeError: class constructors must be invoked with |new|") {
|
|
392
|
+
let inst = new origDestFn(...arguments);
|
|
393
|
+
sourceFn.apply(inst, arguments);
|
|
394
|
+
return inst;
|
|
395
|
+
} else throw e;
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
let destDescriptor = {...sourceDescriptor}; // shallow clone
|
|
400
|
+
destDescriptor[which] = destFn;
|
|
401
|
+
Object.defineProperty(destination, name, destDescriptor);
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
let descriptor = Object.getOwnPropertyDescriptor(source, name);
|
|
405
|
+
if (descriptor) { // if hasOwnProperty
|
|
406
|
+
if (descriptor.get) extendMethod(descriptor, 'get');
|
|
407
|
+
if (descriptor.set) extendMethod(descriptor, 'set');
|
|
408
|
+
if (descriptor.value) extendMethod(descriptor, 'value');
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
} */
|
|
412
|
+
|
|
413
|
+
// TODO: remove this function
|
|
414
|
+
export function mapPixels (mapper, canvas, ctx, x, y, width, height, flush = true) {
|
|
415
|
+
x = x || 0
|
|
416
|
+
y = y || 0
|
|
417
|
+
width = width || canvas.width
|
|
418
|
+
height = height || canvas.height
|
|
419
|
+
const frame = ctx.getImageData(x, y, width, height)
|
|
420
|
+
for (let i = 0, l = frame.data.length; i < l; i += 4) {
|
|
421
|
+
mapper(frame.data, i)
|
|
422
|
+
}
|
|
423
|
+
if (flush) {
|
|
424
|
+
ctx.putImageData(frame, x, y)
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* <p>Emits "change" event when direct public properties updated. Should be called after
|
|
430
|
+
* all prototype methods are defined in class and after all public properties are
|
|
431
|
+
* initialized in constructor.
|
|
432
|
+
* <p>Must be called before any watchable properties are set, and only once in the prototype chain.
|
|
433
|
+
*
|
|
434
|
+
* @param {object} target - object to watch
|
|
435
|
+
*/
|
|
436
|
+
export function watchPublic (target) {
|
|
437
|
+
const getPath = (obj, prop) =>
|
|
438
|
+
(obj === target ? '' : (target.__watchPublicPath + '.')) + prop
|
|
439
|
+
|
|
440
|
+
const callback = function (obj, prop, val) {
|
|
441
|
+
// Public API property updated, emit 'modify' event.
|
|
442
|
+
publish(proxy, `${obj._type}.change.modify`, { property: getPath(obj, prop), newValue: val })
|
|
443
|
+
}
|
|
444
|
+
const check = prop => !(prop.startsWith('_') || target._publicExcludes.includes(prop))
|
|
445
|
+
|
|
446
|
+
const handler = {
|
|
447
|
+
set (obj, prop, val, receiver) {
|
|
448
|
+
// Recurse
|
|
449
|
+
if (typeof val === 'object' && val !== null && !val.__watchPublicPath && check(prop)) {
|
|
450
|
+
val = new Proxy(val, handler)
|
|
451
|
+
val.__watchPublicPath = getPath(obj, prop)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const was = prop in obj
|
|
455
|
+
// set property or attribute
|
|
456
|
+
// Search prototype chain for the closest setter
|
|
457
|
+
let objProto = obj
|
|
458
|
+
while ((objProto = Object.getPrototypeOf(objProto))) {
|
|
459
|
+
const propDesc = Object.getOwnPropertyDescriptor(objProto, prop)
|
|
460
|
+
if (propDesc && propDesc.set) {
|
|
461
|
+
propDesc.set.call(receiver, val) // call setter, supplying proxy as this (fixes event bugs)
|
|
462
|
+
break
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (!objProto) { // couldn't find setter; set value on instance
|
|
466
|
+
obj[prop] = val
|
|
467
|
+
}
|
|
468
|
+
// Check if it already existed and if it's a valid property to watch, if on root object
|
|
469
|
+
if (obj !== target || (was && check(prop))) {
|
|
470
|
+
callback(obj, prop, val)
|
|
471
|
+
}
|
|
472
|
+
return true
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const proxy = new Proxy(target, handler)
|
|
477
|
+
return proxy
|
|
478
|
+
}
|
|
479
|
+
</code></pre>
|
|
480
|
+
</article>
|
|
481
|
+
</section>
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
</div>
|
|
489
|
+
|
|
490
|
+
<br class="clear">
|
|
491
|
+
|
|
492
|
+
<footer>
|
|
493
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.3</a> on Sun Oct 13 2019 15:38:43 GMT-0400 (Eastern Daylight Time) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
494
|
+
</footer>
|
|
495
|
+
|
|
496
|
+
<script>prettyPrint();</script>
|
|
497
|
+
<script src="scripts/polyfill.js"></script>
|
|
498
|
+
<script src="scripts/linenumber.js"></script>
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
</body>
|
|
503
|
+
</html>
|
package/eslint.conf.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
env: {
|
|
3
|
+
browser: true,
|
|
4
|
+
es6: true
|
|
5
|
+
},
|
|
6
|
+
extends: [
|
|
7
|
+
'standard'
|
|
8
|
+
],
|
|
9
|
+
globals: {
|
|
10
|
+
Atomics: 'readonly',
|
|
11
|
+
SharedArrayBuffer: 'readonly'
|
|
12
|
+
},
|
|
13
|
+
parserOptions: {
|
|
14
|
+
ecmaVersion: 2018,
|
|
15
|
+
sourceType: 'module'
|
|
16
|
+
},
|
|
17
|
+
plugins: [
|
|
18
|
+
'html'
|
|
19
|
+
],
|
|
20
|
+
rules: {
|
|
21
|
+
'brace-style': ['error', '1tbs', { allowSingleLine: false }]
|
|
22
|
+
},
|
|
23
|
+
settings: {
|
|
24
|
+
html: {
|
|
25
|
+
indent: '+2'
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<!-- TODO: fix -->
|
|
2
|
+
<!DOCTYPE html>
|
|
3
|
+
<html>
|
|
4
|
+
<head>
|
|
5
|
+
<title>Fun with Etro</title>
|
|
6
|
+
</head>
|
|
7
|
+
<body>
|
|
8
|
+
<script type="module">
|
|
9
|
+
import etro from '../../src/index.js'
|
|
10
|
+
const cols = 2; const rows = 2
|
|
11
|
+
let movie, sample
|
|
12
|
+
|
|
13
|
+
const begin = () => {
|
|
14
|
+
const canvas = document.createElement('canvas')
|
|
15
|
+
document.body.appendChild(canvas)
|
|
16
|
+
movie = new etro.Movie(canvas)
|
|
17
|
+
etro.event.subscribe(movie, 'movie.timeupdate', () => {
|
|
18
|
+
if (movie.currentTime >= 11) {
|
|
19
|
+
movie.pause()
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
sample = document.createElement('video')
|
|
24
|
+
sample.src = '../assets/sample.ogv'
|
|
25
|
+
|
|
26
|
+
sample.onloadedmetadata = addLayers
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const addLayers = () => {
|
|
30
|
+
movie.width = sample.videoWidth // you could also do canvas.width = sample.videoWidth;
|
|
31
|
+
movie.height = sample.videoHeight
|
|
32
|
+
const width = movie.width / cols; const height = movie.height / rows
|
|
33
|
+
let numbLoaded = 0
|
|
34
|
+
|
|
35
|
+
for (let y = 0; y < movie.height; y += height) {
|
|
36
|
+
for (let x = 0; x < movie.width; x += width) {
|
|
37
|
+
const video = document.createElement('video')
|
|
38
|
+
video.src = '../assets/sample.ogv'
|
|
39
|
+
if (x * y) {
|
|
40
|
+
video.onplay = event => {
|
|
41
|
+
console.log(event)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
video.onloadedmetadata = () => {
|
|
46
|
+
const layer = new etro.layer.Video(0, video, {
|
|
47
|
+
x: x, y: y, width: width, height: height
|
|
48
|
+
// mediaStartTime: 0
|
|
49
|
+
}).addEffect(new etro.effect.Channels({
|
|
50
|
+
r: 0.5 + Math.random() * 0.5,
|
|
51
|
+
g: 0.5 + Math.random() * 0.5,
|
|
52
|
+
b: 0.5 + Math.random() * 0.5
|
|
53
|
+
}))
|
|
54
|
+
movie.addLayer(layer)
|
|
55
|
+
numbLoaded++
|
|
56
|
+
|
|
57
|
+
if (numbLoaded === rows * cols) {
|
|
58
|
+
end()
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const end = () => {
|
|
66
|
+
movie
|
|
67
|
+
.addLayer(new etro.layer.Text(0, sample.duration, 'etro', {
|
|
68
|
+
width: movie.width / 2,
|
|
69
|
+
height: movie.height / 2,
|
|
70
|
+
x: movie.width / 4,
|
|
71
|
+
y: movie.height / 4,
|
|
72
|
+
textX: -movie.width / 4 + movie.width / 2,
|
|
73
|
+
textY: -movie.height / 4 + movie.height / 2,
|
|
74
|
+
background: 'rgba(255,0,200,0.4)',
|
|
75
|
+
color: 'rgba(255,255,255,0.7)',
|
|
76
|
+
font: '24px monospace',
|
|
77
|
+
textAlign: 'center',
|
|
78
|
+
textBaseline: 'middle'
|
|
79
|
+
}))
|
|
80
|
+
.play()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
window.addEventListener('load', begin)
|
|
84
|
+
</script>
|
|
85
|
+
</body>
|
|
86
|
+
</html>
|