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.
- package/.github/workflows/nodejs.yml +4 -2
- package/.github/workflows/shipjs-trigger.yml +29 -0
- package/CHANGELOG.md +73 -12
- package/CODE_OF_CONDUCT.md +5 -5
- package/CONTRIBUTING.md +31 -77
- package/README.md +81 -26
- package/dist/effect/base.d.ts +51 -0
- package/dist/effect/brightness.d.ts +16 -0
- package/dist/effect/channels.d.ts +23 -0
- package/dist/effect/chroma-key.d.ts +23 -0
- package/dist/effect/contrast.d.ts +15 -0
- package/dist/effect/elliptical-mask.d.ts +31 -0
- package/dist/effect/gaussian-blur.d.ts +60 -0
- package/dist/effect/grayscale.d.ts +7 -0
- package/dist/effect/index.d.ts +15 -0
- package/dist/effect/pixelate.d.ts +18 -0
- package/dist/effect/shader.d.ts +99 -0
- package/dist/effect/stack.d.ts +23 -0
- package/dist/effect/transform.d.ts +73 -0
- package/dist/etro-cjs.js +9287 -3331
- package/dist/etro-iife.js +9229 -3273
- package/dist/etro.d.ts +7 -0
- package/dist/event.d.ts +35 -0
- package/dist/index.d.ts +6 -0
- package/dist/layer/audio-source.d.ts +24 -0
- package/dist/layer/audio.d.ts +14 -0
- package/dist/layer/base.d.ts +82 -0
- package/dist/layer/image.d.ts +6 -0
- package/dist/layer/index.d.ts +11 -0
- package/dist/layer/text.d.ts +60 -0
- package/dist/layer/video.d.ts +11 -0
- package/dist/layer/visual-source.d.ts +32 -0
- package/dist/layer/visual.d.ts +58 -0
- package/dist/movie.d.ts +192 -0
- package/dist/object.d.ts +12 -0
- package/dist/util.d.ts +125 -0
- package/eslint.conf.js +2 -9
- package/eslint.example-conf.js +9 -0
- package/eslint.test-conf.js +1 -0
- package/eslint.typescript-conf.js +5 -0
- package/examples/application/readme-screenshot.html +16 -17
- package/examples/application/video-player.html +10 -11
- package/examples/application/webcam.html +6 -6
- package/examples/introduction/audio.html +30 -18
- package/examples/introduction/effects.html +37 -14
- package/examples/introduction/export.html +32 -25
- package/examples/introduction/functions.html +6 -4
- package/examples/introduction/hello-world1.html +9 -5
- package/examples/introduction/hello-world2.html +5 -5
- package/examples/introduction/keyframes.html +35 -23
- package/examples/introduction/media.html +26 -18
- package/examples/introduction/text.html +9 -5
- package/karma.conf.js +5 -3
- package/package.json +36 -14
- package/rollup.config.js +15 -4
- package/scripts/gen-effect-samples.html +26 -25
- package/scripts/save-effect-samples.js +14 -15
- package/ship.config.js +80 -0
- package/src/effect/base.ts +115 -0
- package/src/effect/brightness.ts +43 -0
- package/src/effect/channels.ts +50 -0
- package/src/effect/chroma-key.ts +82 -0
- package/src/effect/contrast.ts +42 -0
- package/src/effect/elliptical-mask.ts +75 -0
- package/src/effect/gaussian-blur.ts +232 -0
- package/src/effect/grayscale.ts +34 -0
- package/src/effect/index.ts +22 -0
- package/src/effect/pixelate.ts +58 -0
- package/src/effect/shader.ts +557 -0
- package/src/effect/stack.ts +78 -0
- package/src/effect/transform.ts +193 -0
- package/src/etro.ts +26 -0
- package/src/event.ts +112 -0
- package/src/index.ts +8 -0
- package/src/layer/audio-source.ts +219 -0
- package/src/layer/audio.ts +34 -0
- package/src/layer/base.ts +175 -0
- package/src/layer/image.ts +8 -0
- package/src/layer/index.ts +13 -0
- package/src/layer/text.ts +138 -0
- package/src/layer/video.ts +15 -0
- package/src/layer/visual-source.ts +150 -0
- package/src/layer/visual.ts +197 -0
- package/src/movie.ts +707 -0
- package/src/object.ts +14 -0
- package/src/util.ts +466 -0
- package/tsconfig.json +8 -0
- package/docs/effect.js.html +0 -1215
- package/docs/event.js.html +0 -145
- package/docs/index.html +0 -81
- package/docs/index.js.html +0 -92
- package/docs/layer.js.html +0 -888
- package/docs/module-effect-GaussianBlurComponent.html +0 -345
- package/docs/module-effect.Brightness.html +0 -339
- package/docs/module-effect.Channels.html +0 -319
- package/docs/module-effect.ChromaKey.html +0 -611
- package/docs/module-effect.Contrast.html +0 -339
- package/docs/module-effect.EllipticalMask.html +0 -200
- package/docs/module-effect.GaussianBlur.html +0 -202
- package/docs/module-effect.GaussianBlurHorizontal.html +0 -242
- package/docs/module-effect.GaussianBlurVertical.html +0 -242
- package/docs/module-effect.Pixelate.html +0 -330
- package/docs/module-effect.Shader.html +0 -1227
- package/docs/module-effect.Stack.html +0 -406
- package/docs/module-effect.Transform.Matrix.html +0 -193
- package/docs/module-effect.Transform.html +0 -1174
- package/docs/module-effect.html +0 -148
- package/docs/module-event.html +0 -473
- package/docs/module-index.html +0 -186
- package/docs/module-layer-Media.html +0 -1116
- package/docs/module-layer-MediaMixin.html +0 -164
- package/docs/module-layer.Audio.html +0 -1188
- package/docs/module-layer.Base.html +0 -629
- package/docs/module-layer.Image.html +0 -1421
- package/docs/module-layer.Text.html +0 -1731
- package/docs/module-layer.Video.html +0 -1938
- package/docs/module-layer.Visual.html +0 -1698
- package/docs/module-layer.html +0 -137
- package/docs/module-movie.html +0 -3118
- package/docs/module-util.Color.html +0 -702
- package/docs/module-util.Font.html +0 -395
- package/docs/module-util.html +0 -845
- package/docs/movie.js.html +0 -689
- package/docs/scripts/collapse.js +0 -20
- package/docs/scripts/linenumber.js +0 -25
- package/docs/scripts/nav.js +0 -12
- package/docs/scripts/polyfill.js +0 -4
- package/docs/scripts/prettify/Apache-License-2.0.txt +0 -202
- package/docs/scripts/prettify/lang-css.js +0 -2
- package/docs/scripts/prettify/prettify.js +0 -28
- package/docs/scripts/search.js +0 -83
- package/docs/styles/jsdoc.css +0 -671
- package/docs/styles/prettify.css +0 -79
- package/docs/util.js.html +0 -503
- package/screenshots/2019-08-17_0.png +0 -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 +0 -352
- package/spec/event.spec.js +0 -25
- package/spec/layer.spec.js +0 -150
- package/spec/movie.spec.js +0 -162
- package/spec/util.spec.js +0 -285
- package/src/effect.js +0 -1268
- package/src/event.js +0 -78
- package/src/index.js +0 -23
- package/src/layer.js +0 -897
- package/src/movie.js +0 -637
- 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
|
+
}
|