etro 0.7.0 → 0.8.2

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 (158) hide show
  1. package/.github/workflows/nodejs.yml +4 -2
  2. package/.github/workflows/shipjs-trigger.yml +29 -0
  3. package/CHANGELOG.md +73 -12
  4. package/CODE_OF_CONDUCT.md +5 -5
  5. package/CONTRIBUTING.md +31 -77
  6. package/README.md +81 -26
  7. package/dist/effect/base.d.ts +51 -0
  8. package/dist/effect/brightness.d.ts +16 -0
  9. package/dist/effect/channels.d.ts +23 -0
  10. package/dist/effect/chroma-key.d.ts +23 -0
  11. package/dist/effect/contrast.d.ts +15 -0
  12. package/dist/effect/elliptical-mask.d.ts +31 -0
  13. package/dist/effect/gaussian-blur.d.ts +60 -0
  14. package/dist/effect/grayscale.d.ts +7 -0
  15. package/dist/effect/index.d.ts +15 -0
  16. package/dist/effect/pixelate.d.ts +18 -0
  17. package/dist/effect/shader.d.ts +99 -0
  18. package/dist/effect/stack.d.ts +23 -0
  19. package/dist/effect/transform.d.ts +73 -0
  20. package/dist/etro-cjs.js +9287 -3331
  21. package/dist/etro-iife.js +9229 -3273
  22. package/dist/etro.d.ts +7 -0
  23. package/dist/event.d.ts +35 -0
  24. package/dist/index.d.ts +6 -0
  25. package/dist/layer/audio-source.d.ts +24 -0
  26. package/dist/layer/audio.d.ts +14 -0
  27. package/dist/layer/base.d.ts +82 -0
  28. package/dist/layer/image.d.ts +6 -0
  29. package/dist/layer/index.d.ts +11 -0
  30. package/dist/layer/text.d.ts +60 -0
  31. package/dist/layer/video.d.ts +11 -0
  32. package/dist/layer/visual-source.d.ts +32 -0
  33. package/dist/layer/visual.d.ts +58 -0
  34. package/dist/movie.d.ts +192 -0
  35. package/dist/object.d.ts +12 -0
  36. package/dist/util.d.ts +125 -0
  37. package/eslint.conf.js +2 -9
  38. package/eslint.example-conf.js +9 -0
  39. package/eslint.test-conf.js +1 -0
  40. package/eslint.typescript-conf.js +5 -0
  41. package/examples/application/readme-screenshot.html +16 -17
  42. package/examples/application/video-player.html +10 -11
  43. package/examples/application/webcam.html +6 -6
  44. package/examples/introduction/audio.html +30 -18
  45. package/examples/introduction/effects.html +37 -14
  46. package/examples/introduction/export.html +32 -25
  47. package/examples/introduction/functions.html +6 -4
  48. package/examples/introduction/hello-world1.html +9 -5
  49. package/examples/introduction/hello-world2.html +5 -5
  50. package/examples/introduction/keyframes.html +35 -23
  51. package/examples/introduction/media.html +26 -18
  52. package/examples/introduction/text.html +9 -5
  53. package/karma.conf.js +5 -3
  54. package/package.json +36 -14
  55. package/rollup.config.js +15 -4
  56. package/scripts/gen-effect-samples.html +26 -25
  57. package/scripts/save-effect-samples.js +14 -15
  58. package/ship.config.js +80 -0
  59. package/src/effect/base.ts +115 -0
  60. package/src/effect/brightness.ts +43 -0
  61. package/src/effect/channels.ts +50 -0
  62. package/src/effect/chroma-key.ts +82 -0
  63. package/src/effect/contrast.ts +42 -0
  64. package/src/effect/elliptical-mask.ts +75 -0
  65. package/src/effect/gaussian-blur.ts +232 -0
  66. package/src/effect/grayscale.ts +34 -0
  67. package/src/effect/index.ts +22 -0
  68. package/src/effect/pixelate.ts +58 -0
  69. package/src/effect/shader.ts +557 -0
  70. package/src/effect/stack.ts +78 -0
  71. package/src/effect/transform.ts +193 -0
  72. package/src/etro.ts +26 -0
  73. package/src/event.ts +112 -0
  74. package/src/index.ts +8 -0
  75. package/src/layer/audio-source.ts +219 -0
  76. package/src/layer/audio.ts +34 -0
  77. package/src/layer/base.ts +175 -0
  78. package/src/layer/image.ts +8 -0
  79. package/src/layer/index.ts +13 -0
  80. package/src/layer/text.ts +138 -0
  81. package/src/layer/video.ts +15 -0
  82. package/src/layer/visual-source.ts +150 -0
  83. package/src/layer/visual.ts +197 -0
  84. package/src/movie.ts +707 -0
  85. package/src/object.ts +14 -0
  86. package/src/util.ts +466 -0
  87. package/tsconfig.json +8 -0
  88. package/docs/effect.js.html +0 -1215
  89. package/docs/event.js.html +0 -145
  90. package/docs/index.html +0 -81
  91. package/docs/index.js.html +0 -92
  92. package/docs/layer.js.html +0 -888
  93. package/docs/module-effect-GaussianBlurComponent.html +0 -345
  94. package/docs/module-effect.Brightness.html +0 -339
  95. package/docs/module-effect.Channels.html +0 -319
  96. package/docs/module-effect.ChromaKey.html +0 -611
  97. package/docs/module-effect.Contrast.html +0 -339
  98. package/docs/module-effect.EllipticalMask.html +0 -200
  99. package/docs/module-effect.GaussianBlur.html +0 -202
  100. package/docs/module-effect.GaussianBlurHorizontal.html +0 -242
  101. package/docs/module-effect.GaussianBlurVertical.html +0 -242
  102. package/docs/module-effect.Pixelate.html +0 -330
  103. package/docs/module-effect.Shader.html +0 -1227
  104. package/docs/module-effect.Stack.html +0 -406
  105. package/docs/module-effect.Transform.Matrix.html +0 -193
  106. package/docs/module-effect.Transform.html +0 -1174
  107. package/docs/module-effect.html +0 -148
  108. package/docs/module-event.html +0 -473
  109. package/docs/module-index.html +0 -186
  110. package/docs/module-layer-Media.html +0 -1116
  111. package/docs/module-layer-MediaMixin.html +0 -164
  112. package/docs/module-layer.Audio.html +0 -1188
  113. package/docs/module-layer.Base.html +0 -629
  114. package/docs/module-layer.Image.html +0 -1421
  115. package/docs/module-layer.Text.html +0 -1731
  116. package/docs/module-layer.Video.html +0 -1938
  117. package/docs/module-layer.Visual.html +0 -1698
  118. package/docs/module-layer.html +0 -137
  119. package/docs/module-movie.html +0 -3118
  120. package/docs/module-util.Color.html +0 -702
  121. package/docs/module-util.Font.html +0 -395
  122. package/docs/module-util.html +0 -845
  123. package/docs/movie.js.html +0 -689
  124. package/docs/scripts/collapse.js +0 -20
  125. package/docs/scripts/linenumber.js +0 -25
  126. package/docs/scripts/nav.js +0 -12
  127. package/docs/scripts/polyfill.js +0 -4
  128. package/docs/scripts/prettify/Apache-License-2.0.txt +0 -202
  129. package/docs/scripts/prettify/lang-css.js +0 -2
  130. package/docs/scripts/prettify/prettify.js +0 -28
  131. package/docs/scripts/search.js +0 -83
  132. package/docs/styles/jsdoc.css +0 -671
  133. package/docs/styles/prettify.css +0 -79
  134. package/docs/util.js.html +0 -503
  135. package/screenshots/2019-08-17_0.png +0 -0
  136. package/spec/assets/effect/gaussian-blur-horizontal.png +0 -0
  137. package/spec/assets/effect/gaussian-blur-vertical.png +0 -0
  138. package/spec/assets/effect/original.png +0 -0
  139. package/spec/assets/effect/pixelate.png +0 -0
  140. package/spec/assets/effect/transform/multiply.png +0 -0
  141. package/spec/assets/effect/transform/rotate.png +0 -0
  142. package/spec/assets/effect/transform/scale-fraction.png +0 -0
  143. package/spec/assets/effect/transform/scale.png +0 -0
  144. package/spec/assets/effect/transform/translate-fraction.png +0 -0
  145. package/spec/assets/effect/transform/translate.png +0 -0
  146. package/spec/assets/layer/audio.wav +0 -0
  147. package/spec/assets/layer/image.jpg +0 -0
  148. package/spec/effect.spec.js +0 -352
  149. package/spec/event.spec.js +0 -25
  150. package/spec/layer.spec.js +0 -150
  151. package/spec/movie.spec.js +0 -162
  152. package/spec/util.spec.js +0 -285
  153. package/src/effect.js +0 -1268
  154. package/src/event.js +0 -78
  155. package/src/index.js +0 -23
  156. package/src/layer.js +0 -897
  157. package/src/movie.js +0 -637
  158. package/src/util.js +0 -505
@@ -0,0 +1,557 @@
1
+ import { Visual } from '../layer/index'
2
+ import { Movie } from '../movie'
3
+ import { val } from '../util'
4
+ import { Base } from './base'
5
+
6
+ export interface UniformOptions {
7
+ type?: string
8
+ defaultFloatComponent?: number
9
+ }
10
+
11
+ export interface TextureOptions {
12
+ createUniform?: boolean
13
+ target?
14
+ level?: number
15
+ internalFormat?
16
+ srcFormat?
17
+ srcType?
18
+ wrapS?
19
+ wrapT?
20
+ minFilter?
21
+ magFilter?
22
+ }
23
+
24
+ export interface ShaderOptions {
25
+ fragmentSource?: string
26
+ uniforms?: Record<string, (UniformOptions | string)>
27
+ textures?: Record<string, TextureOptions>
28
+ sourceTextureOptions?: TextureOptions
29
+ }
30
+
31
+ /**
32
+ * A hardware-accelerated pixel mapping using WebGL
33
+ */
34
+ // TODO: can `v_TextureCoord` be replaced by `gl_FragUV`?
35
+ export class Shader extends Base {
36
+ /**
37
+ * WebGL texture units consumed by {@link Shader}
38
+ */
39
+ static INTERNAL_TEXTURE_UNITS = 1
40
+ private static _DEFAULT_TEXTURE_OPTIONS = {
41
+ createUniform: true,
42
+ target: 'TEXTURE_2D',
43
+ level: 0,
44
+ internalFormat: 'RGBA',
45
+ srcFormat: 'RGBA',
46
+ srcType: 'UNSIGNED_BYTE',
47
+ minFilter: 'LINEAR',
48
+ magFilter: 'LINEAR',
49
+ wrapS: 'CLAMP_TO_EDGE',
50
+ wrapT: 'CLAMP_TO_EDGE'
51
+ }
52
+
53
+ private static _VERTEX_SOURCE = `
54
+ attribute vec4 a_VertexPosition;
55
+ attribute vec2 a_TextureCoord;
56
+
57
+ varying highp vec2 v_TextureCoord;
58
+
59
+ void main() {
60
+ // no need for projection or model-view matrices, since we're just rendering a rectangle
61
+ // that fills the screen (see position values)
62
+ gl_Position = a_VertexPosition;
63
+ v_TextureCoord = a_TextureCoord;
64
+ }
65
+ `
66
+ private static _IDENTITY_FRAGMENT_SOURCE = `
67
+ precision mediump float;
68
+
69
+ uniform sampler2D u_Source;
70
+
71
+ varying highp vec2 v_TextureCoord;
72
+
73
+ void main() {
74
+ gl_FragColor = texture2D(u_Source, v_TextureCoord);
75
+ }
76
+ `
77
+ private _program: WebGLProgram
78
+ private _buffers: {
79
+ position: WebGLBuffer,
80
+ textureCoord: WebGLBuffer
81
+ }
82
+
83
+ private _canvas: HTMLCanvasElement
84
+ private _gl: WebGLRenderingContext
85
+ private _uniformLocations: Record<string, WebGLUniformLocation>
86
+ private _attribLocations: Record<string, GLint>
87
+ private _userUniforms: Record<string, (UniformOptions | string)>
88
+ private _userTextures: Record<string, TextureOptions>
89
+ private _sourceTextureOptions: TextureOptions
90
+ private _inputTexture: WebGLTexture
91
+
92
+ /**
93
+ * @param fragmentSrc
94
+ * @param [userUniforms={}] - object mapping uniform id to an
95
+ * options object or a string (if you only need to provide the uniforms'
96
+ * type)
97
+ * @param [userTextures=[]]
98
+ * @param [sourceTextureOptions={}]
99
+ */
100
+ constructor (options: ShaderOptions = {}) {
101
+ super()
102
+ // TODO: split up into multiple methods
103
+ const fragmentSrc = options.fragmentSource || Shader._IDENTITY_FRAGMENT_SOURCE
104
+ const userUniforms = options.uniforms || {}
105
+ const userTextures = options.textures || {}
106
+ const sourceTextureOptions = options.sourceTextureOptions || {}
107
+
108
+ const gl = this._initGl()
109
+ this._program = Shader._initShaderProgram(gl, Shader._VERTEX_SOURCE, fragmentSrc)
110
+ this._buffers = Shader._initRectBuffers(gl)
111
+
112
+ this._initTextures(userUniforms, userTextures, sourceTextureOptions)
113
+ this._initAttribs()
114
+ this._initUniforms(userUniforms)
115
+ }
116
+
117
+ private _initGl () {
118
+ this._canvas = document.createElement('canvas')
119
+ const gl = this._canvas.getContext('webgl')
120
+ if (gl === null)
121
+ throw new Error('Unable to initialize WebGL. Your browser or machine may not support it.')
122
+
123
+ this._gl = gl
124
+ return gl
125
+ }
126
+
127
+ private _initTextures (userUniforms, userTextures, sourceTextureOptions) {
128
+ const gl = this._gl
129
+ const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS)
130
+ if (userTextures.length > maxTextures)
131
+ console.warn('Too many textures!')
132
+
133
+ this._userTextures = {}
134
+ for (const name in userTextures) {
135
+ const userOptions: TextureOptions = userTextures[name]
136
+ // Apply default options.
137
+ const options = { ...Shader._DEFAULT_TEXTURE_OPTIONS, ...userOptions }
138
+
139
+ if (options.createUniform) {
140
+ /*
141
+ * Automatically, create a uniform with the same name as this texture,
142
+ * that points to it. This is an easy way for the user to use custom
143
+ * textures, without having to define multiple properties in the effect
144
+ * object.
145
+ */
146
+ if (userUniforms[name])
147
+ throw new Error(`Texture - uniform naming conflict: ${name}!`)
148
+
149
+ // Add this as a "user uniform".
150
+ userUniforms[name] = '1i' // texture pointer
151
+ }
152
+ this._userTextures[name] = options
153
+ }
154
+ this._sourceTextureOptions = { ...Shader._DEFAULT_TEXTURE_OPTIONS, ...sourceTextureOptions }
155
+ }
156
+
157
+ private _initAttribs () {
158
+ const gl = this._gl
159
+ this._attribLocations = {
160
+ textureCoord: gl.getAttribLocation(this._program, 'a_TextureCoord')
161
+ // a_VertexPosition ?? somehow it works without it though...
162
+ }
163
+ }
164
+
165
+ private _initUniforms (userUniforms) {
166
+ const gl = this._gl
167
+ this._uniformLocations = {
168
+ source: gl.getUniformLocation(this._program, 'u_Source'),
169
+ size: gl.getUniformLocation(this._program, 'u_Size')
170
+ }
171
+ // The options value can just be a string equal to the type of the variable,
172
+ // for syntactic sugar. If this is the case, convert it to a real options
173
+ // object.
174
+ this._userUniforms = {}
175
+ for (const name in userUniforms) {
176
+ const val = userUniforms[name]
177
+ this._userUniforms[name] = typeof val === 'string' ? { type: val } : val
178
+ }
179
+ for (const unprefixed in userUniforms) {
180
+ // property => u_Property
181
+ const prefixed = 'u_' + unprefixed.charAt(0).toUpperCase() + (unprefixed.length > 1 ? unprefixed.slice(1) : '')
182
+ this._uniformLocations[unprefixed] = gl.getUniformLocation(this._program, prefixed)
183
+ }
184
+ }
185
+
186
+ // Not needed, right?
187
+ /* watchWebGLOptions() {
188
+ const pubChange = () => {
189
+ this.publish("change", {});
190
+ };
191
+ for (let name in this._userTextures) {
192
+ watch(this, name, pubChange);
193
+ }
194
+ for (let name in this._userUniforms) {
195
+ watch(this, name, pubChange);
196
+ }
197
+ } */
198
+
199
+ apply (target: Movie | Visual, reltime: number): void {
200
+ this._checkDimensions(target)
201
+ this._refreshGl()
202
+
203
+ this._enablePositionAttrib()
204
+ this._enableTexCoordAttrib()
205
+ this._prepareTextures(target, reltime)
206
+
207
+ this._gl.useProgram(this._program)
208
+
209
+ this._prepareUniforms(target, reltime)
210
+
211
+ this._draw(target)
212
+ }
213
+
214
+ private _checkDimensions (target) {
215
+ const gl = this._gl
216
+ // TODO: Change target.canvas.width => target.width and see if it breaks
217
+ // anything.
218
+ if (this._canvas.width !== target.canvas.width || this._canvas.height !== target.canvas.height) { // (optimization)
219
+ this._canvas.width = target.canvas.width
220
+ this._canvas.height = target.canvas.height
221
+
222
+ gl.viewport(0, 0, target.canvas.width, target.canvas.height)
223
+ }
224
+ }
225
+
226
+ private _refreshGl () {
227
+ const gl = this._gl
228
+ // Clear to black; fragments can be made transparent with the blendfunc
229
+ // below.
230
+ gl.clearColor(0, 0, 0, 1)
231
+ // gl.clearDepth(1.0); // clear everything
232
+ // not sure why I can't multiply rgb by zero
233
+ gl.blendFuncSeparate(gl.SRC_ALPHA, gl.SRC_ALPHA, gl.ONE, gl.ZERO)
234
+ gl.enable(gl.BLEND)
235
+ gl.disable(gl.DEPTH_TEST)
236
+ // gl.depthFunc(gl.LEQUAL);
237
+
238
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
239
+ }
240
+
241
+ private _enablePositionAttrib () {
242
+ const gl = this._gl
243
+ // Tell WebGL how to pull out the positions from buffer
244
+ const numComponents = 2
245
+ // The data in the buffer is 32bit floats
246
+ const type = gl.FLOAT
247
+ // Don't normalize
248
+ const normalize = false
249
+ // How many bytes to get from one set of values to the next
250
+ // 0 = use type and numComponents above
251
+ const stride = 0
252
+ // How many bytes inside the buffer to start from
253
+ const offset = 0
254
+ gl.bindBuffer(gl.ARRAY_BUFFER, this._buffers.position)
255
+ gl.vertexAttribPointer(
256
+ this._attribLocations.vertexPosition,
257
+ numComponents,
258
+ type,
259
+ normalize,
260
+ stride,
261
+ offset)
262
+ gl.enableVertexAttribArray(
263
+ this._attribLocations.vertexPosition)
264
+ }
265
+
266
+ private _enableTexCoordAttrib () {
267
+ const gl = this._gl
268
+ // tell webgl how to pull out the texture coordinates from buffer
269
+ const numComponents = 2 // every coordinate composed of 2 values (uv)
270
+ const type = gl.FLOAT // the data in the buffer is 32 bit float
271
+ const normalize = false // don't normalize
272
+ const stride = 0 // how many bytes to get from one set to the next
273
+ const offset = 0 // how many bytes inside the buffer to start from
274
+ gl.bindBuffer(gl.ARRAY_BUFFER, this._buffers.textureCoord)
275
+ gl.vertexAttribPointer(this._attribLocations.textureCoord, numComponents, type, normalize, stride, offset)
276
+ gl.enableVertexAttribArray(this._attribLocations.textureCoord)
277
+ }
278
+
279
+ private _prepareTextures (target, reltime) {
280
+ const gl = this._gl
281
+ // TODO: figure out which properties should be private / public
282
+
283
+ // Tell WebGL we want to affect texture unit 0
284
+ // Call `activeTexture` before `_loadTexture` so it won't be bound to the
285
+ // last active texture.
286
+ gl.activeTexture(gl.TEXTURE0)
287
+ this._inputTexture = Shader._loadTexture(gl, target.canvas, this._sourceTextureOptions)
288
+ // Bind the texture to texture unit 0
289
+ gl.bindTexture(gl.TEXTURE_2D, this._inputTexture)
290
+
291
+ let i = 0
292
+ for (const name in this._userTextures) {
293
+ const options = this._userTextures[name]
294
+ /*
295
+ * Call `activeTexture` before `_loadTexture` so it won't be bound to the
296
+ * last active texture.
297
+ * TODO: investigate better implementation of `_loadTexture`
298
+ */
299
+ gl.activeTexture(gl.TEXTURE0 + (Shader.INTERNAL_TEXTURE_UNITS + i)) // use the fact that TEXTURE0, TEXTURE1, ... are continuous
300
+ const preparedTex = Shader._loadTexture(gl, val(this, name, reltime), options) // do it every frame to keep updated (I think you need to)
301
+ gl.bindTexture(gl[options.target], preparedTex)
302
+ i++
303
+ }
304
+ }
305
+
306
+ private _prepareUniforms (target, reltime) {
307
+ const gl = this._gl
308
+ // Set the shader uniforms.
309
+
310
+ // Tell the shader we bound the texture to texture unit 0.
311
+ // All base (Shader class) uniforms are optional.
312
+ if (this._uniformLocations.source)
313
+ gl.uniform1i(this._uniformLocations.source, 0)
314
+
315
+ // All base (Shader class) uniforms are optional.
316
+ if (this._uniformLocations.size)
317
+ gl.uniform2iv(this._uniformLocations.size, [target.canvas.width, target.canvas.height])
318
+
319
+ for (const unprefixed in this._userUniforms) {
320
+ const options = this._userUniforms[unprefixed] as UniformOptions
321
+ const value = val(this, unprefixed, reltime)
322
+ const preparedValue = this._prepareValue(value, options.type, reltime, options)
323
+ const location = this._uniformLocations[unprefixed]
324
+ // haHA JavaScript (`options.type` is "1f", for instance)
325
+ gl['uniform' + options.type](location, preparedValue)
326
+ }
327
+ gl.uniform1i(this._uniformLocations.test, 0)
328
+ }
329
+
330
+ private _draw (target) {
331
+ const gl = this._gl
332
+
333
+ const offset = 0
334
+ const vertexCount = 4
335
+ gl.drawArrays(gl.TRIANGLE_STRIP, offset, vertexCount)
336
+
337
+ // clear the target, in case the effect outputs transparent pixels
338
+ target.cctx.clearRect(0, 0, target.canvas.width, target.canvas.height)
339
+ // copy internal image state onto target
340
+ target.cctx.drawImage(this._canvas, 0, 0)
341
+ }
342
+
343
+ /**
344
+ * Converts a value of a standard type for javascript to a standard type for
345
+ * GLSL
346
+ * @param value - the raw value to prepare
347
+ * @param outputType - the WebGL type of |value|; example:
348
+ * <code>1f</code> for a float
349
+ * @param reltime - current time, relative to the target
350
+ * @param [options] - Optional config
351
+ */
352
+ private _prepareValue (value, outputType, reltime, options: UniformOptions = {}) {
353
+ const def = options.defaultFloatComponent || 0
354
+ if (outputType === '1i') {
355
+ /*
356
+ * Textures are passed to the shader by both providing the texture (with
357
+ * texImage2D) and setting the |sampler| uniform equal to the index of
358
+ * the texture. In etro shader effects, the subclass passes the names of
359
+ * all the textures ot this base class, along with all the names of
360
+ * uniforms. By default, corresponding uniforms (with the same name) are
361
+ * created for each texture for ease of use. You can also define
362
+ * different texture properties in the javascript effect by setting it
363
+ * identical to the property with the passed texture name. In WebGL, it
364
+ * will be set to the same integer texture unit.
365
+ *
366
+ * To do this, test if |value| is identical to a texture. If so, set it
367
+ * to the texture's index, so the shader can use it.
368
+ */
369
+ let i = 0
370
+ for (const name in this._userTextures) {
371
+ const testValue = val(this, name, reltime)
372
+ if (value === testValue)
373
+ value = Shader.INTERNAL_TEXTURE_UNITS + i // after the internal texture units
374
+
375
+ i++
376
+ }
377
+ }
378
+
379
+ if (outputType === '3fv') {
380
+ // allow 4-component vectors; TODO: why?
381
+ if (Array.isArray(value) && (value.length === 3 || value.length === 4))
382
+ return value
383
+
384
+ // kind of loose so this can be changed if needed
385
+ if (typeof value === 'object')
386
+ return [
387
+ value.r !== undefined ? value.r : def,
388
+ value.g !== undefined ? value.g : def,
389
+ value.b !== undefined ? value.b : def
390
+ ]
391
+
392
+ throw new Error(`Invalid type: ${outputType} or value: ${value}`)
393
+ }
394
+
395
+ if (outputType === '4fv') {
396
+ if (Array.isArray(value) && value.length === 4)
397
+ return value
398
+
399
+ // kind of loose so this can be changed if needed
400
+ if (typeof value === 'object')
401
+ return [
402
+ value.r !== undefined ? value.r : def,
403
+ value.g !== undefined ? value.g : def,
404
+ value.b !== undefined ? value.b : def,
405
+ value.a !== undefined ? value.a : def
406
+ ]
407
+
408
+ throw new Error(`Invalid type: ${outputType} or value: ${value}`)
409
+ }
410
+
411
+ return value
412
+ }
413
+
414
+ private static _initRectBuffers (gl) {
415
+ const position = [
416
+ // the screen/canvas (output)
417
+ -1.0, 1.0,
418
+ 1.0, 1.0,
419
+ -1.0, -1.0,
420
+ 1.0, -1.0
421
+ ]
422
+ const textureCoord = [
423
+ // the texture/canvas (input)
424
+ 0.0, 0.0,
425
+ 1.0, 0.0,
426
+ 0.0, 1.0,
427
+ 1.0, 1.0
428
+ ]
429
+
430
+ return {
431
+ position: Shader._initBuffer(gl, position),
432
+ textureCoord: Shader._initBuffer(gl, textureCoord)
433
+ }
434
+ }
435
+
436
+ /**
437
+ * Creates the quad covering the screen
438
+ */
439
+ private static _initBuffer (gl, data) {
440
+ const buffer = gl.createBuffer()
441
+
442
+ // Select the buffer as the one to apply buffer operations to from here out.
443
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
444
+
445
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW)
446
+
447
+ return buffer
448
+ }
449
+
450
+ /**
451
+ * Creates a webgl texture from the source.
452
+ * @param [options] - optional WebGL config for texture
453
+ * @param [options.target=gl.TEXTURE_2D]
454
+ * @param [options.level=0]
455
+ * @param [options.internalFormat=gl.RGBA]
456
+ * @param [options.srcFormat=gl.RGBA]
457
+ * @param [options.srcType=gl.UNSIGNED_BYTE]
458
+ * @param [options.wrapS=gl.CLAMP_TO_EDGE]
459
+ * @param [options.wrapT=gl.CLAMP_TO_EDGE]
460
+ * @param [options.minFilter=gl.LINEAR]
461
+ * @param [options.magFilter=gl.LINEAR]
462
+ */
463
+ private static _loadTexture (gl, source, options: TextureOptions = {}) {
464
+ // Apply default options, just in case.
465
+ options = { ...Shader._DEFAULT_TEXTURE_OPTIONS, ...options }
466
+ // When creating the option, the user can't access `gl` so access it here.
467
+ const target = gl[options.target]
468
+ const level = options.level
469
+ const internalFormat = gl[options.internalFormat]
470
+ const srcFormat = gl[options.srcFormat]
471
+ const srcType = gl[options.srcType]
472
+ const wrapS = gl[options.wrapS]
473
+ const wrapT = gl[options.wrapT]
474
+ const minFilter = gl[options.minFilter]
475
+ const magFilter = gl[options.magFilter]
476
+ // TODO: figure out how wrap-s and wrap-t interact with mipmaps
477
+ // (for legacy support)
478
+ // let wrapS = options.wrapS ? options.wrapS : gl.CLAMP_TO_EDGE,
479
+ // wrapT = options.wrapT ? options.wrapT : gl.CLAMP_TO_EDGE;
480
+
481
+ const tex = gl.createTexture()
482
+ gl.bindTexture(target, tex)
483
+
484
+ // gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true) // premultiply alpha
485
+
486
+ // TODO: figure out how this works with layer width/height
487
+
488
+ // TODO: support 3d textures (change texImage2D)
489
+ // set to `source`
490
+ gl.texImage2D(target, level, internalFormat, srcFormat, srcType, source)
491
+
492
+ /*
493
+ * WebGL1 has different requirements for power of 2 images vs non power of 2
494
+ * images so check if the image is a power of 2 in both dimensions. Get
495
+ * dimensions by using the fact that all valid inputs for texImage2D must have
496
+ * `width` and `height` properties except videos, which have `videoWidth` and
497
+ * `videoHeight` instead and `ArrayBufferView`, which is one dimensional (so
498
+ * don't worry about mipmaps)
499
+ */
500
+ const w = target instanceof HTMLVideoElement ? target.videoWidth : target.width
501
+ const h = target instanceof HTMLVideoElement ? target.videoHeight : target.height
502
+ gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, minFilter)
503
+ gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, magFilter)
504
+ if ((w && isPowerOf2(w)) && (h && isPowerOf2(h))) {
505
+ // Yes, it's a power of 2. All wrap modes are valid. Generate mips.
506
+ gl.texParameteri(target, gl.TEXTURE_WRAP_S, wrapS)
507
+ gl.texParameteri(target, gl.TEXTURE_WRAP_T, wrapT)
508
+ gl.generateMipmap(target)
509
+ } else {
510
+ // No, it's not a power of 2. Turn off mips and set
511
+ // wrapping to clamp to edge
512
+ if (wrapS !== gl.CLAMP_TO_EDGE || wrapT !== gl.CLAMP_TO_EDGE)
513
+ console.warn('Wrap mode is not CLAMP_TO_EDGE for a non-power-of-two texture. Defaulting to CLAMP_TO_EDGE')
514
+
515
+ gl.texParameteri(target, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
516
+ gl.texParameteri(target, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
517
+ }
518
+
519
+ return tex
520
+ }
521
+
522
+ private static _initShaderProgram (gl, vertexSrc, fragmentSrc) {
523
+ const vertexShader = Shader._loadShader(gl, gl.VERTEX_SHADER, vertexSrc)
524
+ const fragmentShader = Shader._loadShader(gl, gl.FRAGMENT_SHADER, fragmentSrc)
525
+
526
+ const shaderProgram = gl.createProgram()
527
+ gl.attachShader(shaderProgram, vertexShader)
528
+ gl.attachShader(shaderProgram, fragmentShader)
529
+ gl.linkProgram(shaderProgram)
530
+
531
+ // Check program creation status
532
+ if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
533
+ console.warn('Unable to link shader program: ' + gl.getProgramInfoLog(shaderProgram))
534
+ return null
535
+ }
536
+
537
+ return shaderProgram
538
+ }
539
+
540
+ private static _loadShader (gl, type, source) {
541
+ const shader = gl.createShader(type)
542
+ gl.shaderSource(shader, source)
543
+ gl.compileShader(shader)
544
+
545
+ // Check compile status
546
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
547
+ console.warn('An error occured compiling shader: ' + gl.getShaderInfoLog(shader))
548
+ gl.deleteShader(shader)
549
+ return null
550
+ }
551
+
552
+ return shader
553
+ }
554
+ }
555
+ // Shader.prototype.getpublicExcludes = () =>
556
+
557
+ const isPowerOf2 = value => (value && (value - 1)) === 0
@@ -0,0 +1,78 @@
1
+ import { Movie } from '../movie'
2
+ import { Base } from './base'
3
+ import { Visual } from '../layer'
4
+
5
+ export interface StackOptions {
6
+ effects: Base[]
7
+ }
8
+
9
+ /**
10
+ * A sequence of effects to apply, treated as one effect. This can be useful
11
+ * for defining reused effect sequences as one effect.
12
+ */
13
+ export class Stack extends Base {
14
+ readonly effects: Base[]
15
+
16
+ private _effectsBack: Base[]
17
+
18
+ constructor (options: StackOptions) {
19
+ super()
20
+
21
+ this._effectsBack = []
22
+ // TODO: Throw 'change' events in handlers
23
+ this.effects = new Proxy(this._effectsBack, {
24
+ deleteProperty: function (target: Base[], property: number | string): boolean {
25
+ const value = target[property]
26
+ value.detach() // Detach effect from movie
27
+ delete target[property]
28
+ return true
29
+ },
30
+ set: function (target: Base[], property: number | string, value: Base): boolean {
31
+ // TODO: make sure type check works
32
+ if (!isNaN(Number(property))) { // if property is a number (index)
33
+ if (target[property])
34
+ target[property].detach() // Detach old effect from movie
35
+
36
+ value.attach(this._target) // Attach effect to movie
37
+ }
38
+ target[property] = value
39
+ return true
40
+ }
41
+ })
42
+ options.effects.forEach(effect => this.effects.push(effect))
43
+
44
+ // TODO: Propogate 'change' events from children up
45
+ }
46
+
47
+ attach (movie: Movie): void {
48
+ super.attach(movie)
49
+ this.effects.filter(effect => !!effect).forEach(effect => {
50
+ effect.detach()
51
+ effect.attach(movie)
52
+ })
53
+ }
54
+
55
+ detach (): void {
56
+ super.detach()
57
+ this.effects.filter(effect => !!effect).forEach(effect => {
58
+ effect.detach()
59
+ })
60
+ }
61
+
62
+ apply (target: Movie | Visual, reltime: number): void {
63
+ for (let i = 0; i < this.effects.length; i++) {
64
+ const effect = this.effects[i]
65
+ if (!effect) continue
66
+ effect.apply(target, reltime)
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Convenience method for chaining
72
+ * @param effect - the effect to append
73
+ */
74
+ addEffect (effect: Base): Stack {
75
+ this.effects.push(effect)
76
+ return this
77
+ }
78
+ }