etro 0.8.0 → 0.8.3

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 (97) hide show
  1. package/.github/workflows/nodejs.yml +3 -1
  2. package/.github/workflows/shipjs-trigger.yml +29 -0
  3. package/CHANGELOG.md +36 -13
  4. package/CODE_OF_CONDUCT.md +5 -5
  5. package/CONTRIBUTING.md +22 -72
  6. package/README.md +2 -2
  7. package/dist/effect/base.d.ts +14 -1
  8. package/dist/etro-cjs.js +189 -230
  9. package/dist/etro-iife.js +189 -230
  10. package/dist/layer/base.d.ts +13 -0
  11. package/eslint.conf.js +2 -1
  12. package/eslint.test-conf.js +1 -0
  13. package/examples/application/readme-screenshot.html +4 -8
  14. package/examples/application/video-player.html +3 -4
  15. package/examples/introduction/effects.html +23 -4
  16. package/karma.conf.js +4 -2
  17. package/package.json +8 -4
  18. package/scripts/gen-effect-samples.html +0 -3
  19. package/ship.config.js +80 -0
  20. package/src/effect/base.ts +29 -10
  21. package/src/effect/gaussian-blur.ts +10 -10
  22. package/src/effect/pixelate.ts +1 -2
  23. package/src/effect/shader.ts +18 -22
  24. package/src/effect/stack.ts +8 -4
  25. package/src/effect/transform.ts +13 -14
  26. package/src/event.ts +8 -14
  27. package/src/layer/audio-source.ts +16 -14
  28. package/src/layer/audio.ts +1 -2
  29. package/src/layer/base.ts +26 -7
  30. package/src/layer/visual.ts +11 -6
  31. package/src/movie.ts +70 -64
  32. package/src/util.ts +50 -57
  33. package/docs/effect.js.html +0 -1215
  34. package/docs/event.js.html +0 -145
  35. package/docs/index.html +0 -81
  36. package/docs/index.js.html +0 -92
  37. package/docs/layer.js.html +0 -888
  38. package/docs/module-effect-GaussianBlurComponent.html +0 -345
  39. package/docs/module-effect.Brightness.html +0 -339
  40. package/docs/module-effect.Channels.html +0 -319
  41. package/docs/module-effect.ChromaKey.html +0 -611
  42. package/docs/module-effect.Contrast.html +0 -339
  43. package/docs/module-effect.EllipticalMask.html +0 -200
  44. package/docs/module-effect.GaussianBlur.html +0 -202
  45. package/docs/module-effect.GaussianBlurHorizontal.html +0 -242
  46. package/docs/module-effect.GaussianBlurVertical.html +0 -242
  47. package/docs/module-effect.Pixelate.html +0 -330
  48. package/docs/module-effect.Shader.html +0 -1227
  49. package/docs/module-effect.Stack.html +0 -406
  50. package/docs/module-effect.Transform.Matrix.html +0 -193
  51. package/docs/module-effect.Transform.html +0 -1174
  52. package/docs/module-effect.html +0 -148
  53. package/docs/module-event.html +0 -473
  54. package/docs/module-index.html +0 -186
  55. package/docs/module-layer-Media.html +0 -1116
  56. package/docs/module-layer-MediaMixin.html +0 -164
  57. package/docs/module-layer.Audio.html +0 -1188
  58. package/docs/module-layer.Base.html +0 -629
  59. package/docs/module-layer.Image.html +0 -1421
  60. package/docs/module-layer.Text.html +0 -1731
  61. package/docs/module-layer.Video.html +0 -1938
  62. package/docs/module-layer.Visual.html +0 -1698
  63. package/docs/module-layer.html +0 -137
  64. package/docs/module-movie.html +0 -3118
  65. package/docs/module-util.Color.html +0 -702
  66. package/docs/module-util.Font.html +0 -395
  67. package/docs/module-util.html +0 -845
  68. package/docs/movie.js.html +0 -689
  69. package/docs/scripts/collapse.js +0 -20
  70. package/docs/scripts/linenumber.js +0 -25
  71. package/docs/scripts/nav.js +0 -12
  72. package/docs/scripts/polyfill.js +0 -4
  73. package/docs/scripts/prettify/Apache-License-2.0.txt +0 -202
  74. package/docs/scripts/prettify/lang-css.js +0 -2
  75. package/docs/scripts/prettify/prettify.js +0 -28
  76. package/docs/scripts/search.js +0 -83
  77. package/docs/styles/jsdoc.css +0 -671
  78. package/docs/styles/prettify.css +0 -79
  79. package/docs/util.js.html +0 -503
  80. package/spec/assets/effect/gaussian-blur-horizontal.png +0 -0
  81. package/spec/assets/effect/gaussian-blur-vertical.png +0 -0
  82. package/spec/assets/effect/grayscale.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 +0 -421
  94. package/spec/event.spec.js +0 -39
  95. package/spec/layer.spec.js +0 -307
  96. package/spec/movie.spec.js +0 -346
  97. package/spec/util.spec.js +0 -294
@@ -1,421 +0,0 @@
1
- const RED = new Uint8ClampedArray([255, 0, 0, 255])
2
- const BLUE = new Uint8ClampedArray([0, 0, 255, 255])
3
-
4
- const clamp = (x, a, b) => Math.max(Math.min(x, b), a)
5
-
6
- function createPixel (colorData) {
7
- // Create 1x1 canvas and set pixel to red
8
- const ctx = document.createElement('canvas')
9
- .getContext('2d')
10
- ctx.canvas.width = ctx.canvas.height = 1
11
- const imageData = ctx.createImageData(1, 1)
12
- for (let i = 0; i < imageData.data.length; i++) {
13
- imageData.data[i] = colorData[i]
14
- }
15
- ctx.putImageData(imageData, 0, 0)
16
-
17
- return ctx
18
- }
19
-
20
- /**
21
- * Create a square canvas with random opaque noise
22
- * @param {number} size the width and height
23
- * @return {TestCanvas}
24
- *
25
- * @typedef {Object} TestCanvas
26
- * @property {CanvasRenderingContext2D} ctx
27
- * @property {ImageData} imageData
28
- */
29
- function createRandomCanvas (size) {
30
- const ctx = document.createElement('canvas')
31
- .getContext('2d')
32
- ctx.canvas.width = ctx.canvas.height = size
33
- // Create a grid of random colors
34
- const imageData = ctx.createImageData(ctx.canvas.width, ctx.canvas.height)
35
- // opaque so premultiplied alpha won't mess up the rgb comparisons
36
- const data = imageData.data.map((_, i) => i % 4 === 3 ? 255 : Math.floor(256 * Math.random()))
37
- for (let i = 0; i < data.length; i++) {
38
- imageData.data[i] = data[i]
39
- }
40
- ctx.putImageData(imageData, 0, 0)
41
-
42
- return { ctx, imageData }
43
- }
44
-
45
- function getImageData (path, targetCanvas = undefined) {
46
- return new Promise(resolve => {
47
- targetCanvas = targetCanvas || document.createElement('canvas')
48
- const img = new Image()
49
- img.onload = () => {
50
- targetCanvas.width = img.width
51
- targetCanvas.height = img.height
52
- const ctx = targetCanvas.getContext('2d')
53
- ctx.drawImage(img, 0, 0)
54
- resolve(ctx.getImageData(0, 0, img.width, img.height))
55
- }
56
- img.src = 'base/spec/assets/effect/' + path
57
- })
58
- }
59
-
60
- function copyCanvas (source) {
61
- const dest = document.createElement('canvas')
62
- dest.width = source.width
63
- dest.height = source.height
64
- dest.getContext('2d')
65
- .drawImage(source, 0, 0)
66
- return dest
67
- }
68
-
69
- function compareImageData (original, effect, path) {
70
- return new Promise(resolve => {
71
- const result = copyCanvas(original)
72
- const ctx = result.getContext('2d')
73
- const dummyMovie = new etro.Movie({ canvas: dummyCanvas })
74
- effect.apply({ canvas: result, cctx: ctx, movie: dummyMovie }) // movie should be unique, to prevent caching!
75
- const actual = ctx.getImageData(0, 0, result.width, result.height)
76
-
77
- getImageData(path).then(expected => {
78
- expect(actual).toEqual(expected)
79
- resolve()
80
- })
81
- })
82
- }
83
-
84
- /*
85
- * Don't reload the original image for each test, just once;
86
- * However, Jasmine will exit if we don't start the tests synchronously
87
- * So, start them, and then wait for the original image to load in the
88
- * test
89
- */
90
- const whenOriginalLoaded = (() => {
91
- const original = document.createElement('canvas')
92
- const loadedCallbacks = []
93
- let loaded = false
94
- getImageData('original.png', original).then(data => {
95
- loaded = true
96
- loadedCallbacks.forEach(callback => callback(original))
97
- })
98
-
99
- function whenOriginalLoaded (callback) {
100
- if (!loaded) {
101
- loadedCallbacks.push(callback)
102
- } else {
103
- callback(original)
104
- }
105
- }
106
- return whenOriginalLoaded
107
- })()
108
-
109
- const dummyCanvas = document.createElement('canvas')
110
-
111
- /* TESTS */
112
-
113
- describe('Effects', function () {
114
- describe('Base', function () {
115
- let effect
116
-
117
- beforeEach(function () {
118
- effect = new etro.effect.Base()
119
- })
120
-
121
- it("should be of type 'effect'", function () {
122
- expect(effect.type).toBe('effect')
123
- })
124
-
125
- it('should set _target when attached', function () {
126
- const movie = {}
127
- effect.attach(movie)
128
- expect(effect._target).toBe(movie)
129
- })
130
- })
131
-
132
- describe('Stack', function () {
133
- let stack
134
-
135
- beforeEach(function () {
136
- const effects = [
137
- new etro.effect.Brightness({ brightness: 10 }),
138
- new etro.effect.Contrast({ contrast: 1.5 })
139
- ]
140
- stack = new etro.effect.Stack({ effects })
141
- stack.attach(new etro.Movie({ canvas: dummyCanvas }))
142
- })
143
-
144
- it('should attach its children to the target when attached', function () {
145
- expect(stack.effects.every(child => child._target === stack._target)).toBe(true)
146
- })
147
-
148
- it('should attach a new child', function () {
149
- const child = new etro.effect.Grayscale()
150
- spyOn(child, 'attach')
151
-
152
- stack.effects.push(child)
153
-
154
- expect(child.attach).toHaveBeenCalled()
155
- })
156
-
157
- it('should detach each child that is removed', function () {
158
- const child = stack.effects[0]
159
- spyOn(child, 'detach')
160
-
161
- stack.effects.shift() // remove first element
162
-
163
- expect(child.detach).toHaveBeenCalled()
164
- })
165
-
166
- it('should detach a child that is replaced', function () {
167
- const child = stack.effects[0]
168
- spyOn(child, 'detach')
169
-
170
- stack.effects[0] = new etro.effect.Base()
171
-
172
- expect(child.detach).toHaveBeenCalled()
173
- })
174
-
175
- it('should be the same as applying individual effects', function () {
176
- const original = createRandomCanvas(4).ctx.canvas
177
- const result = copyCanvas(original)
178
- const resultCtx = result.getContext('2d')
179
-
180
- stack.effects.forEach(effect => effect.apply({
181
- canvas: result, cctx: resultCtx, movie: new etro.Movie({ canvas: dummyCanvas })
182
- }))
183
- const expected = resultCtx.getImageData(0, 0, result.width, result.height)
184
-
185
- resultCtx.drawImage(original, 0, 0) // reset
186
- stack.apply({
187
- canvas: result, cctx: resultCtx, movie: new etro.Movie({ canvas: dummyCanvas })
188
- })
189
- const actual = resultCtx.getImageData(0, 0, result.width, result.height)
190
- expect(actual).toEqual(expected)
191
- })
192
-
193
- it('children array should implement common array methods', function () {
194
- const dummy = () => new etro.effect.Base()
195
- const calls = {
196
- concat: [[dummy()]],
197
- every: [layer => true],
198
- includes: [dummy()],
199
- pop: [],
200
- push: [dummy()],
201
- unshift: [dummy()]
202
- }
203
- for (const method in calls) {
204
- const args = calls[method]
205
- const copy = [...stack.effects]
206
- const expectedResult = Array.prototype[method].apply(copy, args)
207
- const actualResult = stack.effects[method](...args)
208
- expect(actualResult).toEqual(expectedResult)
209
- expect(stack.effects).toEqual(copy)
210
- }
211
- })
212
- })
213
-
214
- describe('Shader', function () {
215
- let effect
216
-
217
- beforeEach(function () {
218
- effect = new etro.effect.Shader()
219
- effect._target = new etro.Movie({ canvas: dummyCanvas }) // so val doesn't break because it can't cache (it requires a movie)
220
- })
221
-
222
- it('should construct', function () {})
223
-
224
- it('should not change the target if no arguments are passed', function () {
225
- const { ctx, imageData: originalData } = createRandomCanvas(2)
226
- // apply effect to a fake layer containing `ctx`
227
- const dummyMovie = new etro.Movie({ canvas: dummyCanvas })
228
- effect.apply({ canvas: ctx.canvas, cctx: ctx, movie: dummyMovie })
229
- // Verify no change
230
- const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height)
231
- expect(imageData).toEqual(originalData)
232
- })
233
- })
234
-
235
- describe('Brightness', function () {
236
- it('should change the brightness', function () {
237
- const effect = new etro.effect.Brightness({ brightness: 5 })
238
- effect._target = new etro.Movie({ canvas: dummyCanvas }) // so val doesn't break because it can't cache (it requires a movie)
239
- const ctx = createPixel(RED)
240
- // Apply effect to a fake layer containing `ctx`
241
- const dummyMovie = new etro.Movie({ canvas: dummyCanvas })
242
- effect.apply({ canvas: ctx.canvas, cctx: ctx, movie: dummyMovie })
243
- // Verify brightness changed
244
- const imageData = ctx.getImageData(0, 0, 1, 1)
245
- expect(imageData.data).toEqual(RED.map((c, i) => c % 4 === 3 ? c
246
- : clamp(c + effect.brightness, 0, 255)))
247
- })
248
- })
249
-
250
- describe('Contrast', function () {
251
- it('should change the contrast', function () {
252
- const effect = new etro.effect.Contrast({ contrast: 5 })
253
- effect._target = new etro.Movie({ canvas: dummyCanvas }) // so val doesn't break because it can't cache (it requires a movie)
254
- const ctx = createPixel(RED)
255
- // Apply effect to a fake layer containing `ctx`
256
- const dummyMovie = new etro.Movie({ canvas: dummyCanvas })
257
- effect.apply({ canvas: ctx.canvas, cctx: ctx, movie: dummyMovie })
258
- // Verify brightness changed
259
- const imageData = ctx.getImageData(0, 0, 1, 1)
260
- expect(imageData.data).toEqual(RED.map((c, i) => c % 4 === 3 ? c
261
- : Math.round(clamp(effect.contrast * (c - 255 / 2), 0, 255))))
262
- })
263
- })
264
-
265
- describe('Grayscale', function () {
266
- it('should desaturate the target', function (done) {
267
- const effect = new etro.effect.Grayscale()
268
- effect._target = new etro.Movie({ canvas: dummyCanvas }) // so val doesn't break because it can't cache (it requires a movie)
269
- const path = 'grayscale.png'
270
- whenOriginalLoaded(original =>
271
- compareImageData(original, effect, path).then(done))
272
- })
273
- })
274
-
275
- describe('Channels', function () {
276
- it('should multiply each channel by a constant', function () {
277
- const effect = new etro.effect.Channels({
278
- channels: { r: 0.5, g: 1.25, b: 2 }
279
- })
280
- effect._target = new etro.Movie({ canvas: dummyCanvas }) // so val doesn't break because it can't cache (it requires a movie)
281
- const ctx = createPixel(RED)
282
- // Apply effect to a fake layer containing `ctx`
283
- const dummyMovie = new etro.Movie({ canvas: dummyCanvas })
284
- effect.apply({ canvas: ctx.canvas, cctx: ctx, movie: dummyMovie })
285
- // Verify brightness changed
286
- const imageData = ctx.getImageData(0, 0, 1, 1)
287
- expect(imageData.data).toEqual(new Uint8ClampedArray([
288
- Math.floor((effect.factors.r || 1) * RED[0]),
289
- Math.floor((effect.factors.g || 1) * RED[1]),
290
- Math.floor((effect.factors.b || 1) * RED[2]),
291
- Math.floor((effect.factors.a || 1) * RED[3])
292
- ]))
293
- })
294
- })
295
-
296
- describe('ChromaKey', function () {
297
- let effect
298
-
299
- beforeEach(function () {
300
- effect = new etro.effect.ChromaKey({
301
- target: { r: 250 },
302
- threshold: 5
303
- }) // will hit r=255, because threshold is 5
304
- effect._target = new etro.Movie({ canvas: dummyCanvas }) // so val doesn't break because it can't cache (it requires a movie)
305
- })
306
-
307
- it('should make the target color transparent', function () {
308
- const ctx = createPixel(RED)
309
- // Apply effect to a fake layer containing `ctx`
310
- effect.apply({ canvas: ctx.canvas, cctx: ctx, movie: new etro.Movie({ canvas: dummyCanvas }) })
311
- // Verify brightness changed
312
- const imageData = ctx.getImageData(0, 0, 1, 1)
313
- const alpha = imageData.data[3]
314
- expect(alpha).toBe(0)
315
- })
316
-
317
- it('should not make other colors transparent', function () {
318
- const ctx = createPixel(BLUE)
319
- // Apply effect to a fake layer containing `ctx`
320
- effect.apply({ canvas: ctx.canvas, cctx: ctx, movie: new etro.Movie({ canvas: dummyCanvas }) })
321
- // Verify brightness changed
322
- const imageData = ctx.getImageData(0, 0, 1, 1)
323
- const alpha = imageData.data[3]
324
- expect(alpha).toBe(255)
325
- })
326
- })
327
-
328
- describe('GaussianBlurHorizontal', function () {
329
- it('should blur with 5-pixel radius', function (done) {
330
- const effect = new etro.effect.GaussianBlurHorizontal({ radius: 5 })
331
- effect._target = new etro.Movie({ canvas: dummyCanvas }) // so val doesn't break because it can't cache (it requires a movie)
332
- const path = 'gaussian-blur-horizontal.png'
333
- whenOriginalLoaded(original =>
334
- compareImageData(original, effect, path).then(done))
335
- })
336
- })
337
-
338
- describe('GaussianBlurVertical', function () {
339
- it('should blur with 5-pixel radius', function (done) {
340
- const effect = new etro.effect.GaussianBlurVertical({ radius: 5 })
341
- effect._target = new etro.Movie({ canvas: dummyCanvas }) // so val doesn't break because it can't cache (it requires a movie)
342
- const path = 'gaussian-blur-vertical.png'
343
- whenOriginalLoaded(original =>
344
- compareImageData(original, effect, path).then(done))
345
- })
346
- })
347
-
348
- describe('Pixelate', function () {
349
- it('should decimate to 3-pixel texels', function (done) {
350
- const effect = new etro.effect.Pixelate({ pixelSize: 3 })
351
- effect._target = new etro.Movie({ canvas: dummyCanvas }) // so val doesn't break because it can't cache (it requires a movie)
352
- const path = 'pixelate.png'
353
- whenOriginalLoaded(original =>
354
- compareImageData(original, effect, path).then(done))
355
- })
356
- })
357
-
358
- describe('Transform', function () {
359
- it('should translate', function (done) {
360
- const effect = new etro.effect.Transform({
361
- matrix: new etro.effect.Transform.Matrix().translate(-3, 5)
362
- })
363
- effect._target = new etro.Movie({ canvas: dummyCanvas }) // so val doesn't break because it can't cache (it requires a movie)
364
- const path = 'transform/translate.png'
365
- whenOriginalLoaded(original =>
366
- compareImageData(original, effect, path).then(done))
367
- })
368
-
369
- it('should translate by non-integers', function (done) {
370
- const effect = new etro.effect.Transform({
371
- matrix: new etro.effect.Transform.Matrix().translate(0.5, 0.5)
372
- })
373
- effect._target = new etro.Movie({ canvas: dummyCanvas }) // so val doesn't break because it can't cache (it requires a movie)
374
- const path = 'transform/translate-fraction.png'
375
- whenOriginalLoaded(original =>
376
- compareImageData(original, effect, path).then(done))
377
- })
378
-
379
- it('should scale', function (done) {
380
- const effect = new etro.effect.Transform({
381
- matrix: new etro.effect.Transform.Matrix().scale(2, 2)
382
- })
383
- effect._target = new etro.Movie({ canvas: dummyCanvas }) // so val doesn't break because it can't cache (it requires a movie)
384
- const path = 'transform/scale.png'
385
- whenOriginalLoaded(original =>
386
- compareImageData(original, effect, path).then(done))
387
- })
388
-
389
- it('should scale by non-integers', function (done) {
390
- const effect = new etro.effect.Transform({
391
- matrix: new etro.effect.Transform.Matrix().scale(0.5, 0.5)
392
- })
393
- effect._target = new etro.Movie({ canvas: dummyCanvas }) // so val doesn't break because it can't cache (it requires a movie)
394
- const path = 'transform/scale-fraction.png'
395
- whenOriginalLoaded(original =>
396
- compareImageData(original, effect, path).then(done))
397
- })
398
-
399
- it('should rotate', function (done) {
400
- const effect = new etro.effect.Transform({
401
- matrix: new etro.effect.Transform.Matrix().rotate(Math.PI / 6)
402
- })
403
- effect._target = new etro.Movie({ canvas: dummyCanvas }) // so val doesn't break because it can't cache (it requires a movie)
404
- const path = 'transform/rotate.png'
405
- whenOriginalLoaded(original =>
406
- compareImageData(original, effect, path).then(done))
407
- })
408
-
409
- it('should multiply together', function (done) {
410
- const effect = new etro.effect.Transform({
411
- matrix: new etro.effect.Transform.Matrix()
412
- .scale(2, 2)
413
- .multiply(new etro.effect.Transform.Matrix().translate(-3, 5))
414
- })
415
- effect._target = new etro.Movie({ canvas: dummyCanvas }) // so val doesn't break because it can't cache (it requires a movie)
416
- const path = 'transform/multiply.png'
417
- whenOriginalLoaded(original =>
418
- compareImageData(original, effect, path).then(done))
419
- })
420
- })
421
- })
@@ -1,39 +0,0 @@
1
- describe('Events', function () {
2
- it('should trigger subscribers', function () {
3
- const o = {}
4
-
5
- const types = ['foo.bar.test', 'foo.bar', 'foo']
6
- types.forEach(type => {
7
- etro.event.subscribe(o, type, event => {
8
- expect(event.target).toEqual(o)
9
- notified.push(type)
10
- })
11
- })
12
-
13
- let notified = []
14
- etro.event.publish(o, 'foo.bar.test', {})
15
- expect(notified).toEqual(types)
16
-
17
- notified = []
18
- etro.event.publish(o, 'foo.bar', {})
19
- expect(notified).toEqual(types.slice(1))
20
-
21
- notified = []
22
- etro.event.publish(o, 'foo', {})
23
- expect(notified).toEqual(types.slice(2))
24
- })
25
-
26
- it('unsubscribe removes event listeners', function () {
27
- const o = {}
28
- let listenerCalled = false
29
- const listener = () => {
30
- listenerCalled = true
31
- }
32
-
33
- etro.event.subscribe(o, 'test', listener)
34
- etro.event.unsubscribe(o, listener)
35
- etro.event.publish(o, 'test', {})
36
-
37
- expect(listenerCalled).toBe(false)
38
- })
39
- })