etro 0.7.0 → 0.8.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 (106) hide show
  1. package/.github/workflows/nodejs.yml +1 -1
  2. package/CHANGELOG.md +45 -1
  3. package/CONTRIBUTING.md +23 -19
  4. package/README.md +81 -26
  5. package/dist/effect/base.d.ts +38 -0
  6. package/dist/effect/brightness.d.ts +16 -0
  7. package/dist/effect/channels.d.ts +23 -0
  8. package/dist/effect/chroma-key.d.ts +23 -0
  9. package/dist/effect/contrast.d.ts +15 -0
  10. package/dist/effect/elliptical-mask.d.ts +31 -0
  11. package/dist/effect/gaussian-blur.d.ts +60 -0
  12. package/dist/effect/grayscale.d.ts +7 -0
  13. package/dist/effect/index.d.ts +15 -0
  14. package/dist/effect/pixelate.d.ts +18 -0
  15. package/dist/effect/shader.d.ts +99 -0
  16. package/dist/effect/stack.d.ts +23 -0
  17. package/dist/effect/transform.d.ts +73 -0
  18. package/dist/etro-cjs.js +9337 -3331
  19. package/dist/etro-iife.js +9279 -3273
  20. package/dist/etro.d.ts +7 -0
  21. package/dist/event.d.ts +35 -0
  22. package/dist/index.d.ts +6 -0
  23. package/dist/layer/audio-source.d.ts +24 -0
  24. package/dist/layer/audio.d.ts +14 -0
  25. package/dist/layer/base.d.ts +69 -0
  26. package/dist/layer/image.d.ts +6 -0
  27. package/dist/layer/index.d.ts +11 -0
  28. package/dist/layer/text.d.ts +60 -0
  29. package/dist/layer/video.d.ts +11 -0
  30. package/dist/layer/visual-source.d.ts +32 -0
  31. package/dist/layer/visual.d.ts +58 -0
  32. package/dist/movie.d.ts +192 -0
  33. package/dist/object.d.ts +12 -0
  34. package/dist/util.d.ts +125 -0
  35. package/eslint.conf.js +0 -8
  36. package/eslint.example-conf.js +9 -0
  37. package/eslint.typescript-conf.js +5 -0
  38. package/examples/application/readme-screenshot.html +12 -9
  39. package/examples/application/video-player.html +7 -7
  40. package/examples/application/webcam.html +6 -6
  41. package/examples/introduction/audio.html +30 -18
  42. package/examples/introduction/effects.html +14 -10
  43. package/examples/introduction/export.html +32 -25
  44. package/examples/introduction/functions.html +6 -4
  45. package/examples/introduction/hello-world1.html +9 -5
  46. package/examples/introduction/hello-world2.html +5 -5
  47. package/examples/introduction/keyframes.html +35 -23
  48. package/examples/introduction/media.html +26 -18
  49. package/examples/introduction/text.html +9 -5
  50. package/karma.conf.js +1 -1
  51. package/package.json +29 -13
  52. package/rollup.config.js +15 -4
  53. package/scripts/gen-effect-samples.html +29 -25
  54. package/scripts/save-effect-samples.js +14 -15
  55. package/spec/assets/effect/gaussian-blur-horizontal.png +0 -0
  56. package/spec/assets/effect/gaussian-blur-vertical.png +0 -0
  57. package/spec/assets/effect/grayscale.png +0 -0
  58. package/spec/assets/effect/original.png +0 -0
  59. package/spec/assets/effect/pixelate.png +0 -0
  60. package/spec/assets/effect/transform/multiply.png +0 -0
  61. package/spec/assets/effect/transform/rotate.png +0 -0
  62. package/spec/assets/effect/transform/scale-fraction.png +0 -0
  63. package/spec/assets/effect/transform/scale.png +0 -0
  64. package/spec/assets/effect/transform/translate-fraction.png +0 -0
  65. package/spec/assets/effect/transform/translate.png +0 -0
  66. package/spec/effect.spec.js +126 -57
  67. package/spec/event.spec.js +14 -0
  68. package/spec/layer.spec.js +175 -18
  69. package/spec/movie.spec.js +191 -7
  70. package/spec/util.spec.js +14 -5
  71. package/src/effect/base.ts +96 -0
  72. package/src/effect/brightness.ts +43 -0
  73. package/src/effect/channels.ts +50 -0
  74. package/src/effect/chroma-key.ts +82 -0
  75. package/src/effect/contrast.ts +42 -0
  76. package/src/effect/elliptical-mask.ts +75 -0
  77. package/src/effect/gaussian-blur.ts +232 -0
  78. package/src/effect/grayscale.ts +34 -0
  79. package/src/effect/index.ts +22 -0
  80. package/src/effect/pixelate.ts +59 -0
  81. package/src/effect/shader.ts +561 -0
  82. package/src/effect/stack.ts +74 -0
  83. package/src/effect/transform.ts +194 -0
  84. package/src/etro.ts +26 -0
  85. package/src/event.ts +118 -0
  86. package/src/index.ts +8 -0
  87. package/src/layer/audio-source.ts +217 -0
  88. package/src/layer/audio.ts +35 -0
  89. package/src/layer/base.ts +156 -0
  90. package/src/layer/image.ts +8 -0
  91. package/src/layer/index.ts +13 -0
  92. package/src/layer/text.ts +138 -0
  93. package/src/layer/video.ts +15 -0
  94. package/src/layer/visual-source.ts +150 -0
  95. package/src/layer/visual.ts +198 -0
  96. package/src/movie.ts +709 -0
  97. package/src/object.ts +14 -0
  98. package/src/util.ts +473 -0
  99. package/tsconfig.json +8 -0
  100. package/screenshots/2019-08-17_0.png +0 -0
  101. package/src/effect.js +0 -1268
  102. package/src/event.js +0 -78
  103. package/src/index.js +0 -23
  104. package/src/layer.js +0 -897
  105. package/src/movie.js +0 -637
  106. package/src/util.js +0 -505
@@ -0,0 +1,232 @@
1
+ import { val } from '../util'
2
+ import { Stack } from './stack'
3
+ import { Shader } from './shader'
4
+ import { Movie } from '../movie'
5
+ import { Visual } from '../layer'
6
+
7
+ export interface GaussianBlurOptions {
8
+ radius: number
9
+ }
10
+
11
+ /**
12
+ * Applies a Gaussian blur
13
+ */
14
+ // TODO: Improve performance
15
+ // TODO: Make sure this is truly gaussian even though it doens't require a
16
+ // standard deviation
17
+ export class GaussianBlur extends Stack {
18
+ constructor (options: GaussianBlurOptions) {
19
+ // Divide into two shader effects (use the fact that gaussian blurring can
20
+ // be split into components for performance benefits)
21
+ super({
22
+ effects: [
23
+ new GaussianBlurHorizontal(options),
24
+ new GaussianBlurVertical(options)
25
+ ]
26
+ })
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Shared class for both horizontal and vertical gaussian blur classes.
32
+ */
33
+ // TODO: If radius == 0, don't affect the image (right now, the image goes black).
34
+ class GaussianBlurComponent extends Shader {
35
+ radius: number
36
+ shape: HTMLCanvasElement
37
+
38
+ private _radiusCache: number
39
+
40
+ /**
41
+ * @param src - fragment source code (specific to which component -
42
+ * horizontal or vertical)
43
+ * @param radius - only integers are currently supported
44
+ */
45
+ constructor (options: {
46
+ fragmentSource: string,
47
+ radius: number
48
+ }) {
49
+ super({
50
+ fragmentSource: options.fragmentSource,
51
+ uniforms: {
52
+ radius: '1i'
53
+ },
54
+ textures: {
55
+ shape: { minFilter: 'NEAREST', magFilter: 'NEAREST' }
56
+ }
57
+ })
58
+ /**
59
+ */
60
+ this.radius = options.radius
61
+ this._radiusCache = undefined
62
+ }
63
+
64
+ apply (target: Movie | Visual, reltime: number): void {
65
+ const radiusVal = val(this, 'radius', reltime)
66
+ if (radiusVal !== this._radiusCache) {
67
+ // Regenerate gaussian distribution canvas.
68
+ this.shape = GaussianBlurComponent._render1DKernel(
69
+ GaussianBlurComponent._gen1DKernel(radiusVal)
70
+ )
71
+ }
72
+ this._radiusCache = radiusVal
73
+
74
+ super.apply(target, reltime)
75
+ }
76
+
77
+ /**
78
+ * Render Gaussian kernel to a canvas for use in shader.
79
+ * @param kernel
80
+ * @private
81
+ *
82
+ * @return
83
+ */
84
+ private static _render1DKernel (kernel: number[]): HTMLCanvasElement {
85
+ // TODO: Use Float32Array instead of canvas.
86
+ // init canvas
87
+ const canvas = document.createElement('canvas')
88
+ canvas.width = kernel.length
89
+ canvas.height = 1 // 1-dimensional
90
+ const ctx = canvas.getContext('2d')
91
+
92
+ // draw to canvas
93
+ const imageData = ctx.createImageData(canvas.width, canvas.height)
94
+ for (let i = 0; i < kernel.length; i++) {
95
+ imageData.data[4 * i + 0] = 255 * kernel[i] // Use red channel to store distribution weights.
96
+ imageData.data[4 * i + 1] = 0 // Clear all other channels.
97
+ imageData.data[4 * i + 2] = 0
98
+ imageData.data[4 * i + 3] = 255
99
+ }
100
+ ctx.putImageData(imageData, 0, 0)
101
+
102
+ return canvas
103
+ }
104
+
105
+ private static _gen1DKernel (radius: number): number[] {
106
+ const pascal = GaussianBlurComponent._genPascalRow(2 * radius + 1)
107
+ // don't use `reduce` and `map` (overhead?)
108
+ let sum = 0
109
+ for (let i = 0; i < pascal.length; i++) {
110
+ sum += pascal[i]
111
+ }
112
+ for (let i = 0; i < pascal.length; i++) {
113
+ pascal[i] /= sum
114
+ }
115
+ return pascal
116
+ }
117
+
118
+ private static _genPascalRow (index: number): number[] {
119
+ if (index < 0) {
120
+ throw new Error(`Invalid index ${index}`)
121
+ }
122
+ let currRow = [1]
123
+ for (let i = 1; i < index; i++) {
124
+ const nextRow = []
125
+ nextRow.length = currRow.length + 1
126
+ // edges are always 1's
127
+ nextRow[0] = nextRow[nextRow.length - 1] = 1
128
+ for (let j = 1; j < nextRow.length - 1; j++) {
129
+ nextRow[j] = currRow[j - 1] + currRow[j]
130
+ }
131
+ currRow = nextRow
132
+ }
133
+ return currRow
134
+ }
135
+ }
136
+ GaussianBlurComponent.prototype.publicExcludes = Shader.prototype.publicExcludes.concat(['shape'])
137
+
138
+ /**
139
+ * Horizontal component of gaussian blur
140
+ */
141
+ export class GaussianBlurHorizontal extends GaussianBlurComponent {
142
+ /**
143
+ * @param radius
144
+ */
145
+ constructor (options: GaussianBlurOptions) {
146
+ super({
147
+ fragmentSource: `
148
+ #define MAX_RADIUS 250
149
+
150
+ precision mediump float;
151
+
152
+ uniform sampler2D u_Source;
153
+ uniform ivec2 u_Size; // pixel dimensions of input and output
154
+ uniform sampler2D u_Shape; // pseudo one-dimension of blur distribution (would be 1D but webgl doesn't support it)
155
+ uniform int u_Radius; // TODO: support floating-point radii
156
+
157
+ varying highp vec2 v_TextureCoord;
158
+
159
+ void main() {
160
+ /*
161
+ * Ideally, totalWeight should end up being 1, but due to rounding errors, it sometimes ends up less than 1
162
+ * (I believe JS canvas stores values as integers, which rounds down for the majority of the Gaussian curve)
163
+ * So, normalize by accumulating all the weights and dividing by that.
164
+ */
165
+ float totalWeight = 0.0;
166
+ vec4 avg = vec4(0.0);
167
+ // GLSL can only use constants in for-loop declaration, so start at zero, and stop before 2 * u_Radius + 1,
168
+ // opposed to starting at -u_Radius and stopping _at_ +u_Radius.
169
+ for (int i = 0; i < 2 * MAX_RADIUS + 1; i++) {
170
+ if (i >= 2 * u_Radius + 1)
171
+ break; // GLSL can only use constants in for-loop declaration, so we break here.
172
+ // (2 * u_Radius + 1) is the width of u_Shape, by definition
173
+ float weight = texture2D(u_Shape, vec2(float(i) / float(2 * u_Radius + 1), 0.5)).r; // TODO: use single-channel format
174
+ totalWeight += weight;
175
+ vec4 sample = texture2D(u_Source, v_TextureCoord + vec2(i - u_Radius, 0.0) / vec2(u_Size));
176
+ avg += weight * sample;
177
+ }
178
+ gl_FragColor = avg / totalWeight;
179
+ }
180
+ `,
181
+ radius: options.radius
182
+ })
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Vertical component of gaussian blur
188
+ */
189
+ export class GaussianBlurVertical extends GaussianBlurComponent {
190
+ /**
191
+ * @param radius
192
+ */
193
+ constructor (options: GaussianBlurOptions) {
194
+ super({
195
+ fragmentSource: `
196
+ #define MAX_RADIUS 250
197
+
198
+ precision mediump float;
199
+
200
+ uniform sampler2D u_Source;
201
+ uniform ivec2 u_Size; // pixel dimensions of input and output
202
+ uniform sampler2D u_Shape; // pseudo one-dimension of blur distribution (would be 1D but webgl doesn't support it)
203
+ uniform int u_Radius; // TODO: support floating-point radii
204
+
205
+ varying highp vec2 v_TextureCoord;
206
+
207
+ void main() {
208
+ /*
209
+ * Ideally, totalWeight should end up being 1, but due to rounding errors, it sometimes ends up less than 1
210
+ * (I believe JS canvas stores values as integers, which rounds down for the majority of the Gaussian curve)
211
+ * So, normalize by accumulating all the weights and dividing by that.
212
+ */
213
+ float totalWeight = 0.0;
214
+ vec4 avg = vec4(0.0);
215
+ // GLSL can only use constants in for-loop declaration, so start at zero, and stop before 2 * u_Radius + 1,
216
+ // opposed to starting at -u_Radius and stopping _at_ +u_Radius.
217
+ for (int i = 0; i < 2 * MAX_RADIUS + 1; i++) {
218
+ if (i >= 2 * u_Radius + 1)
219
+ break; // GLSL can only use constants in for-loop declaration, so we break here.
220
+ // (2 * u_Radius + 1) is the width of u_Shape, by definition
221
+ float weight = texture2D(u_Shape, vec2(float(i) / float(2 * u_Radius + 1), 0.5)).r; // TODO: use single-channel format
222
+ totalWeight += weight;
223
+ vec4 sample = texture2D(u_Source, v_TextureCoord + vec2(0.0, i - u_Radius) / vec2(u_Size));
224
+ avg += weight * sample;
225
+ }
226
+ gl_FragColor = avg / totalWeight;
227
+ }
228
+ `,
229
+ radius: options.radius
230
+ })
231
+ }
232
+ }
@@ -0,0 +1,34 @@
1
+ import { Shader } from './shader'
2
+
3
+ /**
4
+ * Converts the target to a grayscale image
5
+ */
6
+ export class Grayscale extends Shader {
7
+ constructor () {
8
+ super({
9
+ fragmentSource: `
10
+ precision mediump float;
11
+
12
+ uniform sampler2D u_Source;
13
+ uniform vec4 u_Factors;
14
+
15
+ varying highp vec2 v_TextureCoord;
16
+
17
+ float max3(float x, float y, float z) {
18
+ return max(x, max(y, z));
19
+ }
20
+
21
+ float min3(float x, float y, float z) {
22
+ return min(x, min(y, z));
23
+ }
24
+
25
+ void main() {
26
+ vec4 color = texture2D(u_Source, v_TextureCoord);
27
+ // Desaturate
28
+ float value = (max3(color.r, color.g, color.b) + min3(color.r, color.g, color.b)) / 2.0;
29
+ gl_FragColor = vec4(value, value, value, color.a);
30
+ }
31
+ `
32
+ })
33
+ }
34
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @module effect
3
+ */
4
+
5
+ // TODO: Investigate why an effect might run once in the beginning even if its layer isn't at the beginning
6
+ // TODO: Add audio effect support
7
+ // TODO: implement directional blur
8
+ // TODO: implement radial blur
9
+ // TODO: implement zoom blur
10
+
11
+ export * from './base'
12
+ export * from './brightness'
13
+ export * from './channels'
14
+ export * from './chroma-key'
15
+ export * from './contrast'
16
+ export * from './elliptical-mask'
17
+ export * from './gaussian-blur'
18
+ export * from './grayscale'
19
+ export * from './pixelate'
20
+ export * from './shader'
21
+ export * from './stack'
22
+ export * from './transform'
@@ -0,0 +1,59 @@
1
+ import { Visual } from '../layer'
2
+ import { Movie } from '../movie'
3
+ import { val, Dynamic } from '../util'
4
+ import { Shader } from './shader'
5
+
6
+ export interface PixelateOptions {
7
+ pixelSize?: Dynamic<number>
8
+ }
9
+
10
+ /**
11
+ * Breaks the target up into squares of `pixelSize` by `pixelSize`
12
+ */
13
+ // TODO: just resample with NEAREST interpolation? but how?
14
+ export class Pixelate extends Shader {
15
+ pixelSize: Dynamic<number>
16
+
17
+ /**
18
+ * @param pixelSize
19
+ */
20
+ constructor (options: PixelateOptions = {}) {
21
+ super({
22
+ fragmentSource: `
23
+ precision mediump float;
24
+
25
+ uniform sampler2D u_Source;
26
+ uniform ivec2 u_Size;
27
+ uniform int u_PixelSize;
28
+
29
+ varying highp vec2 v_TextureCoord;
30
+
31
+ void main() {
32
+ int ps = u_PixelSize;
33
+
34
+ // Snap to nearest block's center
35
+ vec2 loc = vec2(u_Size) * v_TextureCoord; // pixel-space
36
+ vec2 snappedLoc = float(ps) * floor(loc / float(ps));
37
+ vec2 centeredLoc = snappedLoc + vec2(float(u_PixelSize) / 2.0 + 0.5);
38
+ vec2 clampedLoc = clamp(centeredLoc, vec2(0.0), vec2(u_Size));
39
+ gl_FragColor = texture2D(u_Source, clampedLoc / vec2(u_Size));
40
+ }
41
+ `,
42
+ uniforms: {
43
+ pixelSize: '1i'
44
+ }
45
+ })
46
+ /**
47
+ */
48
+ this.pixelSize = options.pixelSize || 1
49
+ }
50
+
51
+ apply (target: Movie | Visual, reltime: number): void {
52
+ const ps = val(this, 'pixelSize', reltime)
53
+ if (ps % 1 !== 0 || ps < 0) {
54
+ throw new Error('Pixel size must be a nonnegative integer')
55
+ }
56
+
57
+ super.apply(target, reltime)
58
+ }
59
+ }