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.
- package/.github/workflows/nodejs.yml +1 -1
- package/CHANGELOG.md +45 -1
- package/CONTRIBUTING.md +23 -19
- package/README.md +81 -26
- package/dist/effect/base.d.ts +38 -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 +9337 -3331
- package/dist/etro-iife.js +9279 -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 +69 -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 +0 -8
- package/eslint.example-conf.js +9 -0
- package/eslint.typescript-conf.js +5 -0
- package/examples/application/readme-screenshot.html +12 -9
- package/examples/application/video-player.html +7 -7
- package/examples/application/webcam.html +6 -6
- package/examples/introduction/audio.html +30 -18
- package/examples/introduction/effects.html +14 -10
- 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 +1 -1
- package/package.json +29 -13
- package/rollup.config.js +15 -4
- package/scripts/gen-effect-samples.html +29 -25
- package/scripts/save-effect-samples.js +14 -15
- package/spec/assets/effect/gaussian-blur-horizontal.png +0 -0
- package/spec/assets/effect/gaussian-blur-vertical.png +0 -0
- package/spec/assets/effect/grayscale.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/effect.spec.js +126 -57
- package/spec/event.spec.js +14 -0
- package/spec/layer.spec.js +175 -18
- package/spec/movie.spec.js +191 -7
- package/spec/util.spec.js +14 -5
- package/src/effect/base.ts +96 -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 +59 -0
- package/src/effect/shader.ts +561 -0
- package/src/effect/stack.ts +74 -0
- package/src/effect/transform.ts +194 -0
- package/src/etro.ts +26 -0
- package/src/event.ts +118 -0
- package/src/index.ts +8 -0
- package/src/layer/audio-source.ts +217 -0
- package/src/layer/audio.ts +35 -0
- package/src/layer/base.ts +156 -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 +198 -0
- package/src/movie.ts +709 -0
- package/src/object.ts +14 -0
- package/src/util.ts +473 -0
- package/tsconfig.json +8 -0
- package/screenshots/2019-08-17_0.png +0 -0
- 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
package/spec/effect.spec.js
CHANGED
|
@@ -70,7 +70,8 @@ function compareImageData (original, effect, path) {
|
|
|
70
70
|
return new Promise(resolve => {
|
|
71
71
|
const result = copyCanvas(original)
|
|
72
72
|
const ctx = result.getContext('2d')
|
|
73
|
-
|
|
73
|
+
const dummyMovie = new etro.Movie({ canvas: dummyCanvas })
|
|
74
|
+
effect.apply({ canvas: result, cctx: ctx, movie: dummyMovie }) // movie should be unique, to prevent caching!
|
|
74
75
|
const actual = ctx.getImageData(0, 0, result.width, result.height)
|
|
75
76
|
|
|
76
77
|
getImageData(path).then(expected => {
|
|
@@ -114,7 +115,7 @@ describe('Effects', function () {
|
|
|
114
115
|
let effect
|
|
115
116
|
|
|
116
117
|
beforeEach(function () {
|
|
117
|
-
effect = new etro.effect.Base(
|
|
118
|
+
effect = new etro.effect.Base()
|
|
118
119
|
})
|
|
119
120
|
|
|
120
121
|
it("should be of type 'effect'", function () {
|
|
@@ -129,19 +130,46 @@ describe('Effects', function () {
|
|
|
129
130
|
})
|
|
130
131
|
|
|
131
132
|
describe('Stack', function () {
|
|
132
|
-
let
|
|
133
|
+
let stack
|
|
133
134
|
|
|
134
135
|
beforeEach(function () {
|
|
135
|
-
effects = [
|
|
136
|
-
new etro.effect.Brightness(10),
|
|
137
|
-
new etro.effect.Contrast(1.5)
|
|
136
|
+
const effects = [
|
|
137
|
+
new etro.effect.Brightness({ brightness: 10 }),
|
|
138
|
+
new etro.effect.Contrast({ contrast: 1.5 })
|
|
138
139
|
]
|
|
139
|
-
stack = new etro.effect.Stack(effects)
|
|
140
|
-
stack.attach(new etro.Movie(dummyCanvas))
|
|
140
|
+
stack = new etro.effect.Stack({ effects })
|
|
141
|
+
stack.attach(new etro.Movie({ canvas: dummyCanvas }))
|
|
141
142
|
})
|
|
142
143
|
|
|
143
144
|
it('should attach its children to the target when attached', function () {
|
|
144
|
-
expect(effects.every(child => child._target === stack._target)).toBe(true)
|
|
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()
|
|
145
173
|
})
|
|
146
174
|
|
|
147
175
|
it('should be the same as applying individual effects', function () {
|
|
@@ -149,18 +177,38 @@ describe('Effects', function () {
|
|
|
149
177
|
const result = copyCanvas(original)
|
|
150
178
|
const resultCtx = result.getContext('2d')
|
|
151
179
|
|
|
152
|
-
effects.forEach(effect => effect.apply({
|
|
153
|
-
canvas: result, cctx: resultCtx, movie: new etro.Movie(dummyCanvas)
|
|
180
|
+
stack.effects.forEach(effect => effect.apply({
|
|
181
|
+
canvas: result, cctx: resultCtx, movie: new etro.Movie({ canvas: dummyCanvas })
|
|
154
182
|
}))
|
|
155
183
|
const expected = resultCtx.getImageData(0, 0, result.width, result.height)
|
|
156
184
|
|
|
157
185
|
resultCtx.drawImage(original, 0, 0) // reset
|
|
158
186
|
stack.apply({
|
|
159
|
-
canvas: result, cctx: resultCtx, movie: new etro.Movie(dummyCanvas)
|
|
187
|
+
canvas: result, cctx: resultCtx, movie: new etro.Movie({ canvas: dummyCanvas })
|
|
160
188
|
})
|
|
161
189
|
const actual = resultCtx.getImageData(0, 0, result.width, result.height)
|
|
162
190
|
expect(actual).toEqual(expected)
|
|
163
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
|
+
})
|
|
164
212
|
})
|
|
165
213
|
|
|
166
214
|
describe('Shader', function () {
|
|
@@ -168,7 +216,7 @@ describe('Effects', function () {
|
|
|
168
216
|
|
|
169
217
|
beforeEach(function () {
|
|
170
218
|
effect = new etro.effect.Shader()
|
|
171
|
-
effect._target = new etro.Movie(dummyCanvas) // so val doesn't break because it can't cache (it requires a movie)
|
|
219
|
+
effect._target = new etro.Movie({ canvas: dummyCanvas }) // so val doesn't break because it can't cache (it requires a movie)
|
|
172
220
|
})
|
|
173
221
|
|
|
174
222
|
it('should construct', function () {})
|
|
@@ -176,7 +224,8 @@ describe('Effects', function () {
|
|
|
176
224
|
it('should not change the target if no arguments are passed', function () {
|
|
177
225
|
const { ctx, imageData: originalData } = createRandomCanvas(2)
|
|
178
226
|
// apply effect to a fake layer containing `ctx`
|
|
179
|
-
|
|
227
|
+
const dummyMovie = new etro.Movie({ canvas: dummyCanvas })
|
|
228
|
+
effect.apply({ canvas: ctx.canvas, cctx: ctx, movie: dummyMovie })
|
|
180
229
|
// Verify no change
|
|
181
230
|
const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height)
|
|
182
231
|
expect(imageData).toEqual(originalData)
|
|
@@ -185,11 +234,12 @@ describe('Effects', function () {
|
|
|
185
234
|
|
|
186
235
|
describe('Brightness', function () {
|
|
187
236
|
it('should change the brightness', function () {
|
|
188
|
-
const effect = new etro.effect.Brightness(5)
|
|
189
|
-
effect._target = new etro.Movie(dummyCanvas) // so val doesn't break because it can't cache (it requires a movie)
|
|
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)
|
|
190
239
|
const ctx = createPixel(RED)
|
|
191
240
|
// Apply effect to a fake layer containing `ctx`
|
|
192
|
-
|
|
241
|
+
const dummyMovie = new etro.Movie({ canvas: dummyCanvas })
|
|
242
|
+
effect.apply({ canvas: ctx.canvas, cctx: ctx, movie: dummyMovie })
|
|
193
243
|
// Verify brightness changed
|
|
194
244
|
const imageData = ctx.getImageData(0, 0, 1, 1)
|
|
195
245
|
expect(imageData.data).toEqual(RED.map((c, i) => c % 4 === 3 ? c
|
|
@@ -199,11 +249,12 @@ describe('Effects', function () {
|
|
|
199
249
|
|
|
200
250
|
describe('Contrast', function () {
|
|
201
251
|
it('should change the contrast', function () {
|
|
202
|
-
const effect = new etro.effect.Contrast(5)
|
|
203
|
-
effect._target = new etro.Movie(dummyCanvas) // so val doesn't break because it can't cache (it requires a movie)
|
|
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)
|
|
204
254
|
const ctx = createPixel(RED)
|
|
205
255
|
// Apply effect to a fake layer containing `ctx`
|
|
206
|
-
|
|
256
|
+
const dummyMovie = new etro.Movie({ canvas: dummyCanvas })
|
|
257
|
+
effect.apply({ canvas: ctx.canvas, cctx: ctx, movie: dummyMovie })
|
|
207
258
|
// Verify brightness changed
|
|
208
259
|
const imageData = ctx.getImageData(0, 0, 1, 1)
|
|
209
260
|
expect(imageData.data).toEqual(RED.map((c, i) => c % 4 === 3 ? c
|
|
@@ -211,13 +262,26 @@ describe('Effects', function () {
|
|
|
211
262
|
})
|
|
212
263
|
})
|
|
213
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
|
+
|
|
214
275
|
describe('Channels', function () {
|
|
215
276
|
it('should multiply each channel by a constant', function () {
|
|
216
|
-
const effect = new etro.effect.Channels({
|
|
217
|
-
|
|
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)
|
|
218
281
|
const ctx = createPixel(RED)
|
|
219
282
|
// Apply effect to a fake layer containing `ctx`
|
|
220
|
-
|
|
283
|
+
const dummyMovie = new etro.Movie({ canvas: dummyCanvas })
|
|
284
|
+
effect.apply({ canvas: ctx.canvas, cctx: ctx, movie: dummyMovie })
|
|
221
285
|
// Verify brightness changed
|
|
222
286
|
const imageData = ctx.getImageData(0, 0, 1, 1)
|
|
223
287
|
expect(imageData.data).toEqual(new Uint8ClampedArray([
|
|
@@ -233,14 +297,17 @@ describe('Effects', function () {
|
|
|
233
297
|
let effect
|
|
234
298
|
|
|
235
299
|
beforeEach(function () {
|
|
236
|
-
effect = new etro.effect.ChromaKey({
|
|
237
|
-
|
|
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)
|
|
238
305
|
})
|
|
239
306
|
|
|
240
307
|
it('should make the target color transparent', function () {
|
|
241
308
|
const ctx = createPixel(RED)
|
|
242
309
|
// Apply effect to a fake layer containing `ctx`
|
|
243
|
-
effect.apply({ canvas: ctx.canvas, cctx: ctx, movie: new etro.Movie(dummyCanvas) })
|
|
310
|
+
effect.apply({ canvas: ctx.canvas, cctx: ctx, movie: new etro.Movie({ canvas: dummyCanvas }) })
|
|
244
311
|
// Verify brightness changed
|
|
245
312
|
const imageData = ctx.getImageData(0, 0, 1, 1)
|
|
246
313
|
const alpha = imageData.data[3]
|
|
@@ -250,7 +317,7 @@ describe('Effects', function () {
|
|
|
250
317
|
it('should not make other colors transparent', function () {
|
|
251
318
|
const ctx = createPixel(BLUE)
|
|
252
319
|
// Apply effect to a fake layer containing `ctx`
|
|
253
|
-
effect.apply({ canvas: ctx.canvas, cctx: ctx, movie: new etro.Movie(dummyCanvas) })
|
|
320
|
+
effect.apply({ canvas: ctx.canvas, cctx: ctx, movie: new etro.Movie({ canvas: dummyCanvas }) })
|
|
254
321
|
// Verify brightness changed
|
|
255
322
|
const imageData = ctx.getImageData(0, 0, 1, 1)
|
|
256
323
|
const alpha = imageData.data[3]
|
|
@@ -260,8 +327,8 @@ describe('Effects', function () {
|
|
|
260
327
|
|
|
261
328
|
describe('GaussianBlurHorizontal', function () {
|
|
262
329
|
it('should blur with 5-pixel radius', function (done) {
|
|
263
|
-
const effect = new etro.effect.GaussianBlurHorizontal(5)
|
|
264
|
-
effect._target = new etro.Movie(dummyCanvas) // so val doesn't break because it can't cache (it requires a movie)
|
|
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)
|
|
265
332
|
const path = 'gaussian-blur-horizontal.png'
|
|
266
333
|
whenOriginalLoaded(original =>
|
|
267
334
|
compareImageData(original, effect, path).then(done))
|
|
@@ -270,8 +337,8 @@ describe('Effects', function () {
|
|
|
270
337
|
|
|
271
338
|
describe('GaussianBlurVertical', function () {
|
|
272
339
|
it('should blur with 5-pixel radius', function (done) {
|
|
273
|
-
const effect = new etro.effect.GaussianBlurVertical(5)
|
|
274
|
-
effect._target = new etro.Movie(dummyCanvas) // so val doesn't break because it can't cache (it requires a movie)
|
|
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)
|
|
275
342
|
const path = 'gaussian-blur-vertical.png'
|
|
276
343
|
whenOriginalLoaded(original =>
|
|
277
344
|
compareImageData(original, effect, path).then(done))
|
|
@@ -280,8 +347,8 @@ describe('Effects', function () {
|
|
|
280
347
|
|
|
281
348
|
describe('Pixelate', function () {
|
|
282
349
|
it('should decimate to 3-pixel texels', function (done) {
|
|
283
|
-
const effect = new etro.effect.Pixelate(3)
|
|
284
|
-
effect._target = new etro.Movie(dummyCanvas) // so val doesn't break because it can't cache (it requires a movie)
|
|
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)
|
|
285
352
|
const path = 'pixelate.png'
|
|
286
353
|
whenOriginalLoaded(original =>
|
|
287
354
|
compareImageData(original, effect, path).then(done))
|
|
@@ -290,60 +357,62 @@ describe('Effects', function () {
|
|
|
290
357
|
|
|
291
358
|
describe('Transform', function () {
|
|
292
359
|
it('should translate', function (done) {
|
|
293
|
-
const effect = new etro.effect.Transform(
|
|
294
|
-
new etro.effect.Transform.Matrix().translate(-3, 5)
|
|
295
|
-
)
|
|
296
|
-
effect._target = new etro.Movie(dummyCanvas) // so val doesn't break because it can't cache (it requires a movie)
|
|
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)
|
|
297
364
|
const path = 'transform/translate.png'
|
|
298
365
|
whenOriginalLoaded(original =>
|
|
299
366
|
compareImageData(original, effect, path).then(done))
|
|
300
367
|
})
|
|
301
368
|
|
|
302
369
|
it('should translate by non-integers', function (done) {
|
|
303
|
-
const effect = new etro.effect.Transform(
|
|
304
|
-
new etro.effect.Transform.Matrix().translate(0.5, 0.5)
|
|
305
|
-
)
|
|
306
|
-
effect._target = new etro.Movie(dummyCanvas) // so val doesn't break because it can't cache (it requires a movie)
|
|
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)
|
|
307
374
|
const path = 'transform/translate-fraction.png'
|
|
308
375
|
whenOriginalLoaded(original =>
|
|
309
376
|
compareImageData(original, effect, path).then(done))
|
|
310
377
|
})
|
|
311
378
|
|
|
312
379
|
it('should scale', function (done) {
|
|
313
|
-
const effect = new etro.effect.Transform(
|
|
314
|
-
new etro.effect.Transform.Matrix().scale(2, 2)
|
|
315
|
-
)
|
|
316
|
-
effect._target = new etro.Movie(dummyCanvas) // so val doesn't break because it can't cache (it requires a movie)
|
|
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)
|
|
317
384
|
const path = 'transform/scale.png'
|
|
318
385
|
whenOriginalLoaded(original =>
|
|
319
386
|
compareImageData(original, effect, path).then(done))
|
|
320
387
|
})
|
|
321
388
|
|
|
322
389
|
it('should scale by non-integers', function (done) {
|
|
323
|
-
const effect = new etro.effect.Transform(
|
|
324
|
-
new etro.effect.Transform.Matrix().scale(0.5, 0.5)
|
|
325
|
-
)
|
|
326
|
-
effect._target = new etro.Movie(dummyCanvas) // so val doesn't break because it can't cache (it requires a movie)
|
|
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)
|
|
327
394
|
const path = 'transform/scale-fraction.png'
|
|
328
395
|
whenOriginalLoaded(original =>
|
|
329
396
|
compareImageData(original, effect, path).then(done))
|
|
330
397
|
})
|
|
331
398
|
|
|
332
399
|
it('should rotate', function (done) {
|
|
333
|
-
const effect = new etro.effect.Transform(
|
|
334
|
-
new etro.effect.Transform.Matrix().rotate(Math.PI / 6)
|
|
335
|
-
)
|
|
336
|
-
effect._target = new etro.Movie(dummyCanvas) // so val doesn't break because it can't cache (it requires a movie)
|
|
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)
|
|
337
404
|
const path = 'transform/rotate.png'
|
|
338
405
|
whenOriginalLoaded(original =>
|
|
339
406
|
compareImageData(original, effect, path).then(done))
|
|
340
407
|
})
|
|
341
408
|
|
|
342
409
|
it('should multiply together', function (done) {
|
|
343
|
-
const effect = new etro.effect.Transform(
|
|
344
|
-
new etro.effect.Transform.Matrix()
|
|
345
|
-
.
|
|
346
|
-
|
|
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)
|
|
347
416
|
const path = 'transform/multiply.png'
|
|
348
417
|
whenOriginalLoaded(original =>
|
|
349
418
|
compareImageData(original, effect, path).then(done))
|
package/spec/event.spec.js
CHANGED
|
@@ -22,4 +22,18 @@ describe('Events', function () {
|
|
|
22
22
|
etro.event.publish(o, 'foo', {})
|
|
23
23
|
expect(notified).toEqual(types.slice(2))
|
|
24
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
|
+
})
|
|
25
39
|
})
|
package/spec/layer.spec.js
CHANGED
|
@@ -3,7 +3,7 @@ describe('Layers', function () {
|
|
|
3
3
|
let layer
|
|
4
4
|
|
|
5
5
|
beforeEach(function () {
|
|
6
|
-
layer = new etro.layer.Base(0, 4)
|
|
6
|
+
layer = new etro.layer.Base({ startTime: 0, duration: 4 })
|
|
7
7
|
})
|
|
8
8
|
|
|
9
9
|
it("should be of type 'layer'", function () {
|
|
@@ -37,11 +37,35 @@ describe('Layers', function () {
|
|
|
37
37
|
let layer
|
|
38
38
|
|
|
39
39
|
beforeEach(function () {
|
|
40
|
-
layer = new etro.layer.Visual(0, 4,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
)
|
|
40
|
+
layer = new etro.layer.Visual({ startTime: 0, duration: 4, background: 'blue' })
|
|
41
|
+
const movie = { width: 400, height: 400, currentTime: 0, propertyFilters: {} }
|
|
42
|
+
movie.movie = movie
|
|
43
|
+
layer.attach(movie)
|
|
44
44
|
layer.render(0)
|
|
45
|
+
// Clear cache populated by render()
|
|
46
|
+
etro.clearCachedValues(movie)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it("should use the movie's width when no layer width is given", function () {
|
|
50
|
+
const width = etro.val(layer, 'width', 0)
|
|
51
|
+
expect(width).toBe(layer.movie.width)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it("should use the movie's height when no layer height is given", function () {
|
|
55
|
+
const height = etro.val(layer, 'height', 0)
|
|
56
|
+
expect(height).toBe(layer.movie.height)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('should use the width if provided', function () {
|
|
60
|
+
layer.width = 4
|
|
61
|
+
const width = etro.val(layer, 'width', 0)
|
|
62
|
+
expect(width).toBe(4)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('should use the height if provided', function () {
|
|
66
|
+
layer.height = 4
|
|
67
|
+
const height = etro.val(layer, 'height', 0)
|
|
68
|
+
expect(height).toBe(4)
|
|
45
69
|
})
|
|
46
70
|
|
|
47
71
|
it('should render the background', function () {
|
|
@@ -56,36 +80,96 @@ describe('Layers', function () {
|
|
|
56
80
|
}
|
|
57
81
|
expect(allBlue).toBe(true)
|
|
58
82
|
})
|
|
83
|
+
|
|
84
|
+
it('should call `attach` when an effect is added', function () {
|
|
85
|
+
const effect = new etro.effect.Base()
|
|
86
|
+
spyOn(effect, 'attach')
|
|
87
|
+
layer.effects.push(effect)
|
|
88
|
+
expect(effect.attach).toHaveBeenCalled()
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('should call `detach` when an effect is removed', function () {
|
|
92
|
+
const effect = new etro.effect.Base()
|
|
93
|
+
layer.effects.push(effect)
|
|
94
|
+
spyOn(effect, 'detach')
|
|
95
|
+
layer.effects.pop()
|
|
96
|
+
expect(effect.detach).toHaveBeenCalled()
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('should call `detach` when an effect is replaced', function () {
|
|
100
|
+
const effect = new etro.effect.Base()
|
|
101
|
+
layer.effects.push(effect)
|
|
102
|
+
spyOn(effect, 'detach')
|
|
103
|
+
layer.effects[0] = new etro.effect.Base()
|
|
104
|
+
expect(effect.detach).toHaveBeenCalled()
|
|
105
|
+
})
|
|
59
106
|
})
|
|
60
107
|
|
|
61
|
-
describe('
|
|
108
|
+
describe('VisualSource', function () {
|
|
109
|
+
const CustomVisualSource = etro.layer.VisualSourceMixin(etro.layer.Visual)
|
|
62
110
|
let layer
|
|
63
111
|
|
|
64
112
|
beforeEach(function (done) {
|
|
65
113
|
const image = new Image()
|
|
66
114
|
image.src = '/base/spec/assets/layer/image.jpg'
|
|
67
115
|
image.onload = () => {
|
|
68
|
-
layer = new
|
|
116
|
+
layer = new CustomVisualSource({ startTime: 0, duration: 4, source: image })
|
|
69
117
|
// Simulate attach to movie
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
)
|
|
118
|
+
const movie = { width: image.width, height: image.height, currentTime: 0, propertyFilters: [] }
|
|
119
|
+
movie.movie = movie
|
|
120
|
+
layer.attach(movie)
|
|
73
121
|
done()
|
|
74
122
|
}
|
|
75
123
|
})
|
|
76
124
|
|
|
125
|
+
it("should use the image source's width when no sourceWidth is provided", function () {
|
|
126
|
+
const sourceWidth = etro.val(layer, 'sourceWidth', 0)
|
|
127
|
+
expect(sourceWidth).toBe(layer.source.width)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it("should use the image source's height when no sourceHeight is provided", function () {
|
|
131
|
+
const sourceHeight = etro.val(layer, 'sourceHeight', 0)
|
|
132
|
+
expect(sourceHeight).toBe(layer.source.height)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('should use sourceWidth when no destWidth is provided', function () {
|
|
136
|
+
const destWidth = etro.val(layer, 'destWidth', 0)
|
|
137
|
+
const sourceWidth = etro.val(layer, 'sourceWidth', 0)
|
|
138
|
+
expect(destWidth).toBe(sourceWidth)
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('should use sourceHeight when no destHeight is provided', function () {
|
|
142
|
+
const destHeight = etro.val(layer, 'destHeight', 0)
|
|
143
|
+
const sourceHeight = etro.val(layer, 'sourceHeight', 0)
|
|
144
|
+
expect(destHeight).toBe(sourceHeight)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('should use destWidth when no width is provided', function () {
|
|
148
|
+
const width = etro.val(layer, 'width', 0)
|
|
149
|
+
const destWidth = etro.val(layer, 'destWidth', 0)
|
|
150
|
+
expect(width).toBe(destWidth)
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('should use destHeight when no height is provided', function () {
|
|
154
|
+
const height = etro.val(layer, 'height', 0)
|
|
155
|
+
const destHeight = etro.val(layer, 'destHeight', 0)
|
|
156
|
+
expect(height).toBe(destHeight)
|
|
157
|
+
})
|
|
158
|
+
|
|
77
159
|
it('should render', function () {
|
|
78
160
|
// Render layer (actual outcome)
|
|
79
161
|
layer.render(0)
|
|
80
|
-
const
|
|
162
|
+
const width = etro.val(layer, 'width', 0)
|
|
163
|
+
const height = etro.val(layer, 'height', 0)
|
|
164
|
+
const imageData = layer.cctx.getImageData(0, 0, width, height)
|
|
81
165
|
|
|
82
166
|
// Draw image (expected outcome)
|
|
83
167
|
const testCanv = document.createElement('canvas')
|
|
84
|
-
testCanv.width =
|
|
85
|
-
testCanv.height =
|
|
168
|
+
testCanv.width = width
|
|
169
|
+
testCanv.height = height
|
|
86
170
|
const testCtx = testCanv.getContext('2d')
|
|
87
|
-
testCtx.drawImage(layer.
|
|
88
|
-
const testImageData = testCtx.getImageData(0, 0,
|
|
171
|
+
testCtx.drawImage(layer.source, 0, 0, width, height)
|
|
172
|
+
const testImageData = testCtx.getImageData(0, 0, width, height)
|
|
89
173
|
|
|
90
174
|
// Compare expected outcome with actual outcome
|
|
91
175
|
let equal = true
|
|
@@ -94,12 +178,75 @@ describe('Layers', function () {
|
|
|
94
178
|
}
|
|
95
179
|
expect(equal).toBe(true)
|
|
96
180
|
})
|
|
181
|
+
|
|
182
|
+
it('should scale with `imageWidth` and `imageHeight`', function () {
|
|
183
|
+
const resizedLayer = new etro.layer.Image({
|
|
184
|
+
startTime: 0,
|
|
185
|
+
duration: 1,
|
|
186
|
+
source: layer.source,
|
|
187
|
+
destWidth: 100,
|
|
188
|
+
destHeight: 100
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
// Render layer (actual outcome)
|
|
192
|
+
const movie = {}
|
|
193
|
+
resizedLayer.attach(movie)
|
|
194
|
+
resizedLayer.render(0)
|
|
195
|
+
const imageData = resizedLayer.cctx.getImageData(0, 0, resizedLayer.destWidth, resizedLayer.destHeight)
|
|
196
|
+
|
|
197
|
+
// Draw image (expected outcome)
|
|
198
|
+
const testCanv = document.createElement('canvas')
|
|
199
|
+
testCanv.width = resizedLayer.destWidth
|
|
200
|
+
testCanv.height = resizedLayer.destHeight
|
|
201
|
+
const testCtx = testCanv.getContext('2d')
|
|
202
|
+
testCtx.drawImage(layer.source, 0, 0, resizedLayer.destWidth, resizedLayer.destHeight)
|
|
203
|
+
const testImageData = testCtx.getImageData(0, 0, resizedLayer.destWidth, resizedLayer.destHeight)
|
|
204
|
+
|
|
205
|
+
// Compare expected outcome with actual outcome
|
|
206
|
+
expect(imageData.data).toEqual(testImageData.data)
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it('should be cropped to the clip with and height', function () {
|
|
210
|
+
const newLayer = new etro.layer.Image({
|
|
211
|
+
startTime: 0,
|
|
212
|
+
duration: 1,
|
|
213
|
+
source: layer.source,
|
|
214
|
+
sourceWidth: 2,
|
|
215
|
+
sourceHeight: 3
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
// Render layer (actual outcome)
|
|
219
|
+
const movie = {}
|
|
220
|
+
newLayer.attach(movie)
|
|
221
|
+
newLayer.render(0)
|
|
222
|
+
const imageData = newLayer.cctx.getImageData(
|
|
223
|
+
0, 0, newLayer.sourceWidth, newLayer.sourceHeight
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
// Draw image (expected outcome)
|
|
227
|
+
// testCanv will contain the part of the layer with the image.
|
|
228
|
+
const testCanv = document.createElement('canvas')
|
|
229
|
+
testCanv.width = newLayer.sourceWidth
|
|
230
|
+
testCanv.height = newLayer.sourceHeight
|
|
231
|
+
const testCtx = testCanv.getContext('2d')
|
|
232
|
+
testCtx.drawImage(
|
|
233
|
+
layer.source,
|
|
234
|
+
0, 0,
|
|
235
|
+
newLayer.sourceWidth, newLayer.sourceHeight,
|
|
236
|
+
0, 0,
|
|
237
|
+
newLayer.sourceWidth, newLayer.sourceHeight
|
|
238
|
+
)
|
|
239
|
+
const testImageData = testCtx.getImageData(0, 0, newLayer.sourceWidth, newLayer.sourceHeight)
|
|
240
|
+
|
|
241
|
+
// Compare expected image data with actual image data
|
|
242
|
+
expect(imageData.data).toEqual(testImageData.data)
|
|
243
|
+
})
|
|
97
244
|
})
|
|
98
245
|
|
|
99
246
|
describe('Media', function () {
|
|
100
247
|
let layer
|
|
101
248
|
// Media is an abstract mixin, so make a concrete subclass here.
|
|
102
|
-
const CustomMedia = etro.layer.
|
|
249
|
+
const CustomMedia = etro.layer.AudioSourceMixin(etro.layer.Base)
|
|
103
250
|
const source = new Audio()
|
|
104
251
|
|
|
105
252
|
beforeAll(function (done) {
|
|
@@ -108,7 +255,17 @@ describe('Layers', function () {
|
|
|
108
255
|
})
|
|
109
256
|
|
|
110
257
|
beforeEach(function () {
|
|
111
|
-
layer = new CustomMedia(0, source)
|
|
258
|
+
layer = new CustomMedia({ startTime: 0, source })
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
it('should update its currentTime when the movie seeks', function () {
|
|
262
|
+
const movie = {
|
|
263
|
+
actx: new AudioContext(),
|
|
264
|
+
currentTime: 2 // not 0
|
|
265
|
+
}
|
|
266
|
+
layer.attach(movie)
|
|
267
|
+
etro.event.publish(movie, 'movie.seek', {})
|
|
268
|
+
expect(layer.currentTime).toBe(2)
|
|
112
269
|
})
|
|
113
270
|
|
|
114
271
|
it('should have its duration depend on its playbackRate', function () {
|
|
@@ -137,7 +294,7 @@ describe('Layers', function () {
|
|
|
137
294
|
|
|
138
295
|
it('should play', function () {
|
|
139
296
|
let timesPlayed = 0
|
|
140
|
-
layer.
|
|
297
|
+
layer.source.addEventListener('play', () => {
|
|
141
298
|
timesPlayed++
|
|
142
299
|
})
|
|
143
300
|
for (let i = 0; i < 3; i++) {
|