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.
Files changed (103) hide show
  1. package/.env +2 -0
  2. package/.github/workflows/nodejs.yml +27 -0
  3. package/CHANGELOG.md +109 -0
  4. package/CODE_OF_CONDUCT.md +77 -0
  5. package/CONTRIBUTING.md +155 -0
  6. package/LICENSE +674 -0
  7. package/README.md +57 -0
  8. package/dist/etro.js +3397 -0
  9. package/docs/effect.js.html +1215 -0
  10. package/docs/event.js.html +145 -0
  11. package/docs/index.html +81 -0
  12. package/docs/index.js.html +92 -0
  13. package/docs/layer.js.html +888 -0
  14. package/docs/module-effect-GaussianBlurComponent.html +345 -0
  15. package/docs/module-effect.Brightness.html +339 -0
  16. package/docs/module-effect.Channels.html +319 -0
  17. package/docs/module-effect.ChromaKey.html +611 -0
  18. package/docs/module-effect.Contrast.html +339 -0
  19. package/docs/module-effect.EllipticalMask.html +200 -0
  20. package/docs/module-effect.GaussianBlur.html +202 -0
  21. package/docs/module-effect.GaussianBlurHorizontal.html +242 -0
  22. package/docs/module-effect.GaussianBlurVertical.html +242 -0
  23. package/docs/module-effect.Pixelate.html +330 -0
  24. package/docs/module-effect.Shader.html +1227 -0
  25. package/docs/module-effect.Stack.html +406 -0
  26. package/docs/module-effect.Transform.Matrix.html +193 -0
  27. package/docs/module-effect.Transform.html +1174 -0
  28. package/docs/module-effect.html +148 -0
  29. package/docs/module-event.html +473 -0
  30. package/docs/module-index.html +186 -0
  31. package/docs/module-layer-Media.html +1116 -0
  32. package/docs/module-layer-MediaMixin.html +164 -0
  33. package/docs/module-layer.Audio.html +1188 -0
  34. package/docs/module-layer.Base.html +629 -0
  35. package/docs/module-layer.Image.html +1421 -0
  36. package/docs/module-layer.Text.html +1731 -0
  37. package/docs/module-layer.Video.html +1938 -0
  38. package/docs/module-layer.Visual.html +1698 -0
  39. package/docs/module-layer.html +137 -0
  40. package/docs/module-movie.html +3118 -0
  41. package/docs/module-util.Color.html +702 -0
  42. package/docs/module-util.Font.html +395 -0
  43. package/docs/module-util.html +845 -0
  44. package/docs/movie.js.html +689 -0
  45. package/docs/scripts/collapse.js +20 -0
  46. package/docs/scripts/linenumber.js +25 -0
  47. package/docs/scripts/nav.js +12 -0
  48. package/docs/scripts/polyfill.js +4 -0
  49. package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
  50. package/docs/scripts/prettify/lang-css.js +2 -0
  51. package/docs/scripts/prettify/prettify.js +28 -0
  52. package/docs/scripts/search.js +83 -0
  53. package/docs/styles/jsdoc.css +671 -0
  54. package/docs/styles/prettify.css +79 -0
  55. package/docs/util.js.html +503 -0
  56. package/eslint.conf.js +28 -0
  57. package/eslint.test-conf.js +4 -0
  58. package/examples/application/readme-screenshot.html +86 -0
  59. package/examples/application/video-player.html +131 -0
  60. package/examples/application/webcam.html +28 -0
  61. package/examples/introduction/audio.html +52 -0
  62. package/examples/introduction/effects.html +56 -0
  63. package/examples/introduction/export.html +70 -0
  64. package/examples/introduction/functions.html +35 -0
  65. package/examples/introduction/hello-world1.html +33 -0
  66. package/examples/introduction/hello-world2.html +32 -0
  67. package/examples/introduction/keyframes.html +67 -0
  68. package/examples/introduction/media.html +55 -0
  69. package/examples/introduction/text.html +27 -0
  70. package/jsdoc.conf.json +3 -0
  71. package/karma.conf.js +60 -0
  72. package/package.json +63 -0
  73. package/private-todo.txt +70 -0
  74. package/rename-file.sh +18 -0
  75. package/rename-versions.sh +14 -0
  76. package/rename.sh +22 -0
  77. package/rollup.config.js +31 -0
  78. package/screenshots/2019-08-17_0.png +0 -0
  79. package/scripts/gen-effect-samples.html +99 -0
  80. package/scripts/save-effect-samples.js +43 -0
  81. package/spec/assets/effect/gaussian-blur-horizontal.png +0 -0
  82. package/spec/assets/effect/gaussian-blur-vertical.png +0 -0
  83. package/spec/assets/effect/original.png +0 -0
  84. package/spec/assets/effect/pixelate.png +0 -0
  85. package/spec/assets/effect/transform/multiply.png +0 -0
  86. package/spec/assets/effect/transform/rotate.png +0 -0
  87. package/spec/assets/effect/transform/scale-fraction.png +0 -0
  88. package/spec/assets/effect/transform/scale.png +0 -0
  89. package/spec/assets/effect/transform/translate-fraction.png +0 -0
  90. package/spec/assets/effect/transform/translate.png +0 -0
  91. package/spec/assets/layer/audio.wav +0 -0
  92. package/spec/assets/layer/image.jpg +0 -0
  93. package/spec/effect.spec.js +352 -0
  94. package/spec/event.spec.js +25 -0
  95. package/spec/layer.spec.js +128 -0
  96. package/spec/movie.spec.js +154 -0
  97. package/spec/util.spec.js +285 -0
  98. package/src/effect.js +1265 -0
  99. package/src/event.js +78 -0
  100. package/src/index.js +23 -0
  101. package/src/layer.js +875 -0
  102. package/src/movie.js +636 -0
  103. 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 &lt;code>Object&lt;/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)) &amp;&amp; (excludeObjectClass ? obj.constructor.name !== 'Object' : true))
92
+ return props
93
+ }
94
+
95
+ /**
96
+ * @return {boolean} &lt;code>true&lt;/code> if &lt;code>property&lt;/code> is a non-array object and all of its own
97
+ * property keys are numbers or &lt;code>"interpolate"&lt;/code> or &lt;code>"interpolationKeys"&lt;/code>, and
98
+ * &lt;code>false&lt;/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 &lt; 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) &amp;&amp; !(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 &amp;&amp; Object.entries(property).length === 0
117
+ return !isEmpty
118
+ }
119
+
120
+ /**
121
+ * Calculates the value of keyframe set &lt;code>property&lt;/code> at &lt;code>time&lt;/code> if
122
+ * &lt;code>property&lt;/code> is an array, or returns &lt;code>property&lt;/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 &lt;= keyTime &amp;&amp; keyTime &lt;= time) {
157
+ lowerValue = keyValue
158
+ lowerTime = keyTime
159
+ }
160
+ if (time &lt;= keyTime &amp;&amp; keyTime &lt;= 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' &amp;&amp; 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 &lt; 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' &amp;&amp; typeof x1 !== 'object') {
238
+ return x1
239
+ } // flat interpolation (floor)
240
+ if (typeof x1 === 'object' &amp;&amp; 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 &lt; 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/}&lt;br>
359
+ *
360
+ * &lt;strong>Note that the caller has to explicitly update the class value and as well as the class's property
361
+ * &lt;code>constructor&lt;/code> to its prototype's constructor.&lt;/strong>&lt;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 &lt; l; i += 4) {
421
+ mapper(frame.data, i)
422
+ }
423
+ if (flush) {
424
+ ctx.putImageData(frame, x, y)
425
+ }
426
+ }
427
+
428
+ /**
429
+ * &lt;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
+ * &lt;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' &amp;&amp; val !== null &amp;&amp; !val.__watchPublicPath &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; 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,4 @@
1
+ const conf = require('./eslint.conf.js')
2
+ conf.env.jasmine = true
3
+ conf.globals.etro = 'readonly'
4
+ module.exports = conf
@@ -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>