etro 0.7.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/.github/workflows/nodejs.yml +4 -2
  2. package/.github/workflows/shipjs-trigger.yml +29 -0
  3. package/CHANGELOG.md +73 -12
  4. package/CODE_OF_CONDUCT.md +5 -5
  5. package/CONTRIBUTING.md +31 -77
  6. package/README.md +81 -26
  7. package/dist/effect/base.d.ts +51 -0
  8. package/dist/effect/brightness.d.ts +16 -0
  9. package/dist/effect/channels.d.ts +23 -0
  10. package/dist/effect/chroma-key.d.ts +23 -0
  11. package/dist/effect/contrast.d.ts +15 -0
  12. package/dist/effect/elliptical-mask.d.ts +31 -0
  13. package/dist/effect/gaussian-blur.d.ts +60 -0
  14. package/dist/effect/grayscale.d.ts +7 -0
  15. package/dist/effect/index.d.ts +15 -0
  16. package/dist/effect/pixelate.d.ts +18 -0
  17. package/dist/effect/shader.d.ts +99 -0
  18. package/dist/effect/stack.d.ts +23 -0
  19. package/dist/effect/transform.d.ts +73 -0
  20. package/dist/etro-cjs.js +9287 -3331
  21. package/dist/etro-iife.js +9229 -3273
  22. package/dist/etro.d.ts +7 -0
  23. package/dist/event.d.ts +35 -0
  24. package/dist/index.d.ts +6 -0
  25. package/dist/layer/audio-source.d.ts +24 -0
  26. package/dist/layer/audio.d.ts +14 -0
  27. package/dist/layer/base.d.ts +82 -0
  28. package/dist/layer/image.d.ts +6 -0
  29. package/dist/layer/index.d.ts +11 -0
  30. package/dist/layer/text.d.ts +60 -0
  31. package/dist/layer/video.d.ts +11 -0
  32. package/dist/layer/visual-source.d.ts +32 -0
  33. package/dist/layer/visual.d.ts +58 -0
  34. package/dist/movie.d.ts +192 -0
  35. package/dist/object.d.ts +12 -0
  36. package/dist/util.d.ts +125 -0
  37. package/eslint.conf.js +2 -9
  38. package/eslint.example-conf.js +9 -0
  39. package/eslint.test-conf.js +1 -0
  40. package/eslint.typescript-conf.js +5 -0
  41. package/examples/application/readme-screenshot.html +16 -17
  42. package/examples/application/video-player.html +10 -11
  43. package/examples/application/webcam.html +6 -6
  44. package/examples/introduction/audio.html +30 -18
  45. package/examples/introduction/effects.html +37 -14
  46. package/examples/introduction/export.html +32 -25
  47. package/examples/introduction/functions.html +6 -4
  48. package/examples/introduction/hello-world1.html +9 -5
  49. package/examples/introduction/hello-world2.html +5 -5
  50. package/examples/introduction/keyframes.html +35 -23
  51. package/examples/introduction/media.html +26 -18
  52. package/examples/introduction/text.html +9 -5
  53. package/karma.conf.js +5 -3
  54. package/package.json +36 -14
  55. package/rollup.config.js +15 -4
  56. package/scripts/gen-effect-samples.html +26 -25
  57. package/scripts/save-effect-samples.js +14 -15
  58. package/ship.config.js +80 -0
  59. package/src/effect/base.ts +115 -0
  60. package/src/effect/brightness.ts +43 -0
  61. package/src/effect/channels.ts +50 -0
  62. package/src/effect/chroma-key.ts +82 -0
  63. package/src/effect/contrast.ts +42 -0
  64. package/src/effect/elliptical-mask.ts +75 -0
  65. package/src/effect/gaussian-blur.ts +232 -0
  66. package/src/effect/grayscale.ts +34 -0
  67. package/src/effect/index.ts +22 -0
  68. package/src/effect/pixelate.ts +58 -0
  69. package/src/effect/shader.ts +557 -0
  70. package/src/effect/stack.ts +78 -0
  71. package/src/effect/transform.ts +193 -0
  72. package/src/etro.ts +26 -0
  73. package/src/event.ts +112 -0
  74. package/src/index.ts +8 -0
  75. package/src/layer/audio-source.ts +219 -0
  76. package/src/layer/audio.ts +34 -0
  77. package/src/layer/base.ts +175 -0
  78. package/src/layer/image.ts +8 -0
  79. package/src/layer/index.ts +13 -0
  80. package/src/layer/text.ts +138 -0
  81. package/src/layer/video.ts +15 -0
  82. package/src/layer/visual-source.ts +150 -0
  83. package/src/layer/visual.ts +197 -0
  84. package/src/movie.ts +707 -0
  85. package/src/object.ts +14 -0
  86. package/src/util.ts +466 -0
  87. package/tsconfig.json +8 -0
  88. package/docs/effect.js.html +0 -1215
  89. package/docs/event.js.html +0 -145
  90. package/docs/index.html +0 -81
  91. package/docs/index.js.html +0 -92
  92. package/docs/layer.js.html +0 -888
  93. package/docs/module-effect-GaussianBlurComponent.html +0 -345
  94. package/docs/module-effect.Brightness.html +0 -339
  95. package/docs/module-effect.Channels.html +0 -319
  96. package/docs/module-effect.ChromaKey.html +0 -611
  97. package/docs/module-effect.Contrast.html +0 -339
  98. package/docs/module-effect.EllipticalMask.html +0 -200
  99. package/docs/module-effect.GaussianBlur.html +0 -202
  100. package/docs/module-effect.GaussianBlurHorizontal.html +0 -242
  101. package/docs/module-effect.GaussianBlurVertical.html +0 -242
  102. package/docs/module-effect.Pixelate.html +0 -330
  103. package/docs/module-effect.Shader.html +0 -1227
  104. package/docs/module-effect.Stack.html +0 -406
  105. package/docs/module-effect.Transform.Matrix.html +0 -193
  106. package/docs/module-effect.Transform.html +0 -1174
  107. package/docs/module-effect.html +0 -148
  108. package/docs/module-event.html +0 -473
  109. package/docs/module-index.html +0 -186
  110. package/docs/module-layer-Media.html +0 -1116
  111. package/docs/module-layer-MediaMixin.html +0 -164
  112. package/docs/module-layer.Audio.html +0 -1188
  113. package/docs/module-layer.Base.html +0 -629
  114. package/docs/module-layer.Image.html +0 -1421
  115. package/docs/module-layer.Text.html +0 -1731
  116. package/docs/module-layer.Video.html +0 -1938
  117. package/docs/module-layer.Visual.html +0 -1698
  118. package/docs/module-layer.html +0 -137
  119. package/docs/module-movie.html +0 -3118
  120. package/docs/module-util.Color.html +0 -702
  121. package/docs/module-util.Font.html +0 -395
  122. package/docs/module-util.html +0 -845
  123. package/docs/movie.js.html +0 -689
  124. package/docs/scripts/collapse.js +0 -20
  125. package/docs/scripts/linenumber.js +0 -25
  126. package/docs/scripts/nav.js +0 -12
  127. package/docs/scripts/polyfill.js +0 -4
  128. package/docs/scripts/prettify/Apache-License-2.0.txt +0 -202
  129. package/docs/scripts/prettify/lang-css.js +0 -2
  130. package/docs/scripts/prettify/prettify.js +0 -28
  131. package/docs/scripts/search.js +0 -83
  132. package/docs/styles/jsdoc.css +0 -671
  133. package/docs/styles/prettify.css +0 -79
  134. package/docs/util.js.html +0 -503
  135. package/screenshots/2019-08-17_0.png +0 -0
  136. package/spec/assets/effect/gaussian-blur-horizontal.png +0 -0
  137. package/spec/assets/effect/gaussian-blur-vertical.png +0 -0
  138. package/spec/assets/effect/original.png +0 -0
  139. package/spec/assets/effect/pixelate.png +0 -0
  140. package/spec/assets/effect/transform/multiply.png +0 -0
  141. package/spec/assets/effect/transform/rotate.png +0 -0
  142. package/spec/assets/effect/transform/scale-fraction.png +0 -0
  143. package/spec/assets/effect/transform/scale.png +0 -0
  144. package/spec/assets/effect/transform/translate-fraction.png +0 -0
  145. package/spec/assets/effect/transform/translate.png +0 -0
  146. package/spec/assets/layer/audio.wav +0 -0
  147. package/spec/assets/layer/image.jpg +0 -0
  148. package/spec/effect.spec.js +0 -352
  149. package/spec/event.spec.js +0 -25
  150. package/spec/layer.spec.js +0 -150
  151. package/spec/movie.spec.js +0 -162
  152. package/spec/util.spec.js +0 -285
  153. package/src/effect.js +0 -1268
  154. package/src/event.js +0 -78
  155. package/src/index.js +0 -23
  156. package/src/layer.js +0 -897
  157. package/src/movie.js +0 -637
  158. package/src/util.js +0 -505
@@ -0,0 +1,43 @@
1
+ import { Dynamic } from '../util'
2
+ import { Shader } from './shader'
3
+
4
+ export interface BrightnessOptions {
5
+ brightness?: Dynamic<number>
6
+ }
7
+
8
+ /**
9
+ * Changes the brightness
10
+ */
11
+ export class Brightness extends Shader {
12
+ brightness: Dynamic<number>
13
+
14
+ /**
15
+ * @param [brightness=0] - the value to add to each pixel's color
16
+ * channels (between -255 and 255)
17
+ */
18
+ constructor (options: BrightnessOptions = {}) {
19
+ super({
20
+ fragmentSource: `
21
+ precision mediump float;
22
+
23
+ uniform sampler2D u_Source;
24
+ uniform float u_Brightness;
25
+
26
+ varying highp vec2 v_TextureCoord;
27
+
28
+ void main() {
29
+ vec4 color = texture2D(u_Source, v_TextureCoord);
30
+ vec3 rgb = clamp(color.rgb + u_Brightness / 255.0, 0.0, 1.0);
31
+ gl_FragColor = vec4(rgb, color.a);
32
+ }
33
+ `,
34
+ uniforms: {
35
+ brightness: '1f'
36
+ }
37
+ })
38
+ /**
39
+ * The value to add to each pixel's color channels (between -255 and 255)
40
+ */
41
+ this.brightness = options.brightness || 0
42
+ }
43
+ }
@@ -0,0 +1,50 @@
1
+ import { Dynamic } from '../util'
2
+ import { Shader } from './shader'
3
+
4
+ export interface ChannelsOptions {
5
+ factors?: Dynamic<{
6
+ r?: number,
7
+ g?: number,
8
+ b?: number
9
+ }>
10
+ }
11
+
12
+ /**
13
+ * Multiplies each channel by a different factor
14
+ */
15
+ export class Channels extends Shader {
16
+ factors: Dynamic<{
17
+ r?: number,
18
+ b?: number,
19
+ g?: number
20
+ }>
21
+
22
+ /**
23
+ * @param factors - channel factors, each defaulting to 1
24
+ */
25
+ constructor (options: ChannelsOptions = {}) {
26
+ super({
27
+ fragmentSource: `
28
+ precision mediump float;
29
+
30
+ uniform sampler2D u_Source;
31
+ uniform vec4 u_Factors;
32
+
33
+ varying highp vec2 v_TextureCoord;
34
+
35
+ void main() {
36
+ vec4 color = texture2D(u_Source, v_TextureCoord);
37
+ gl_FragColor = clamp(u_Factors * color, 0.0, 1.0);
38
+ }
39
+ `,
40
+ uniforms: {
41
+ factors: { type: '4fv', defaultFloatComponent: 1 }
42
+ }
43
+ })
44
+
45
+ /**
46
+ * Channel factors, each defaulting to 1
47
+ */
48
+ this.factors = options.factors || {}
49
+ }
50
+ }
@@ -0,0 +1,82 @@
1
+ import { Dynamic, Color } from '../util'
2
+ import { Shader } from './shader'
3
+
4
+ export interface ChromaKeyOptions {
5
+ target?: Dynamic<Color>
6
+ threshold?: Dynamic<number>
7
+ interpolate?: Dynamic<boolean>
8
+ // smoothingSharpness
9
+ }
10
+
11
+ /**
12
+ * Reduces alpha for pixels which are close to a specified target color
13
+ */
14
+ export class ChromaKey extends Shader {
15
+ target: Dynamic<Color>
16
+ threshold: Dynamic<number>
17
+ interpolate: Dynamic<boolean>
18
+
19
+ /**
20
+ * @param [target={r: 0, g: 0, b: 0, a: 1}] - the color to remove
21
+ * @param [threshold=0] - how much error to allow
22
+ * @param [interpolate=false] - <code>true</code> to interpolate
23
+ * the alpha channel, or <code>false</code> value for no smoothing (i.e. an
24
+ * alpha of either 0 or 255)
25
+ */
26
+ // TODO: Use <code>smoothingSharpness</code>
27
+ constructor (options: ChromaKeyOptions = {}) {
28
+ super({
29
+ fragmentSource: `
30
+ precision mediump float;
31
+
32
+ uniform sampler2D u_Source;
33
+ uniform vec3 u_Target;
34
+ uniform float u_Threshold;
35
+ uniform bool u_Interpolate;
36
+
37
+ varying highp vec2 v_TextureCoord;
38
+
39
+ void main() {
40
+ vec4 color = texture2D(u_Source, v_TextureCoord);
41
+ float alpha = color.a;
42
+ vec3 dist = abs(color.rgb - u_Target / 255.0);
43
+ if (!u_Interpolate) {
44
+ // Standard way that most video editors probably use (all-or-nothing method)
45
+ float thresh = u_Threshold / 255.0;
46
+ bool transparent = dist.r <= thresh && dist.g <= thresh && dist.b <= thresh;
47
+ if (transparent)
48
+ alpha = 0.0;
49
+ } else {
50
+ /*
51
+ better way IMHO:
52
+ Take the average of the absolute differences between the pixel and the target for each channel
53
+ */
54
+ float transparency = (dist.r + dist.g + dist.b) / 3.0;
55
+ // TODO: custom or variety of interpolation methods
56
+ alpha = transparency;
57
+ }
58
+ gl_FragColor = vec4(color.rgb, alpha);
59
+ }
60
+ `,
61
+ uniforms: {
62
+ target: '3fv',
63
+ threshold: '1f',
64
+ interpolate: '1i'
65
+ }
66
+ })
67
+ /**
68
+ * The color to remove
69
+ */
70
+ this.target = options.target || new Color(0, 0, 0)
71
+ /**
72
+ * How much error to allow
73
+ */
74
+ this.threshold = options.threshold || 0
75
+ /**
76
+ * <code>true<code> to interpolate the alpha channel, or <code>false<code>
77
+ * for no smoothing (i.e. 255 or 0 alpha)
78
+ */
79
+ this.interpolate = options.interpolate || false
80
+ // this.smoothingSharpness = smoothingSharpness;
81
+ }
82
+ }
@@ -0,0 +1,42 @@
1
+ import { Dynamic } from '../util'
2
+ import { Shader } from './shader'
3
+
4
+ export interface ContrastOptions {
5
+ contrast?: Dynamic<number>
6
+ }
7
+
8
+ /**
9
+ * Changes the contrast by multiplying the RGB channels by a constant
10
+ */
11
+ export class Contrast extends Shader {
12
+ contrast: Dynamic<number>
13
+
14
+ /**
15
+ * @param [contrast=1] - the contrast multiplier
16
+ */
17
+ constructor (options: ContrastOptions = {}) {
18
+ super({
19
+ fragmentSource: `
20
+ precision mediump float;
21
+
22
+ uniform sampler2D u_Source;
23
+ uniform float u_Contrast;
24
+
25
+ varying highp vec2 v_TextureCoord;
26
+
27
+ void main() {
28
+ vec4 color = texture2D(u_Source, v_TextureCoord);
29
+ vec3 rgb = clamp(u_Contrast * (color.rgb - 0.5) + 0.5, 0.0, 1.0);
30
+ gl_FragColor = vec4(rgb, color.a);
31
+ }
32
+ `,
33
+ uniforms: {
34
+ contrast: '1f'
35
+ }
36
+ })
37
+ /**
38
+ * The contrast multiplier
39
+ */
40
+ this.contrast = options.contrast || 1
41
+ }
42
+ }
@@ -0,0 +1,75 @@
1
+ import { Movie } from '../movie'
2
+ import { Dynamic, val } from '../util'
3
+ import { Base } from './base'
4
+ import { Visual } from '../layer/index'
5
+
6
+ export class EllipticalMaskOptions {
7
+ x: Dynamic<number>
8
+ y: Dynamic<number>
9
+ radiusX: Dynamic<number>
10
+ radiusY: Dynamic<number>
11
+ rotation?: Dynamic<number>
12
+ startAngle?: Dynamic<number>
13
+ endAngle?: Dynamic<number>
14
+ anticlockwise?: Dynamic<boolean>
15
+ }
16
+
17
+ /**
18
+ * Preserves an ellipse of the layer and clears the rest
19
+ */
20
+ // TODO: Parent layer mask effects will make more complex masks easier
21
+ export class EllipticalMask extends Base {
22
+ x: Dynamic<number>
23
+ y: Dynamic<number>
24
+ radiusX: Dynamic<number>
25
+ radiusY: Dynamic<number>
26
+ rotation: Dynamic<number>
27
+ startAngle: Dynamic<number>
28
+ endAngle: Dynamic<number>
29
+ anticlockwise: Dynamic<boolean>
30
+
31
+ private _tmpCanvas
32
+ private _tmpCtx
33
+
34
+ constructor (options: EllipticalMaskOptions) {
35
+ super()
36
+ this.x = options.x
37
+ this.y = options.y
38
+ this.radiusX = options.radiusX
39
+ this.radiusY = options.radiusY
40
+ this.rotation = options.rotation || 0
41
+ this.startAngle = options.startAngle || 0
42
+ this.endAngle = options.endAngle !== undefined ? options.endAngle : 2 * Math.PI
43
+ this.anticlockwise = options.anticlockwise || false
44
+ // for saving image data before clearing
45
+ this._tmpCanvas = document.createElement('canvas')
46
+ this._tmpCtx = this._tmpCanvas.getContext('2d')
47
+ }
48
+
49
+ apply (target: Movie | Visual, reltime: number): void {
50
+ const ctx = target.cctx
51
+ const canvas = target.canvas
52
+ const x = val(this, 'x', reltime)
53
+ const y = val(this, 'y', reltime)
54
+ const radiusX = val(this, 'radiusX', reltime)
55
+ const radiusY = val(this, 'radiusY', reltime)
56
+ const rotation = val(this, 'rotation', reltime)
57
+ const startAngle = val(this, 'startAngle', reltime)
58
+ const endAngle = val(this, 'endAngle', reltime)
59
+ const anticlockwise = val(this, 'anticlockwise', reltime)
60
+ this._tmpCanvas.width = target.canvas.width
61
+ this._tmpCanvas.height = target.canvas.height
62
+ this._tmpCtx.drawImage(canvas, 0, 0)
63
+
64
+ ctx.clearRect(0, 0, canvas.width, canvas.height)
65
+ ctx.save() // idk how to preserve clipping state without save/restore
66
+ // create elliptical path and clip
67
+ ctx.beginPath()
68
+ ctx.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)
69
+ ctx.closePath()
70
+ ctx.clip()
71
+ // render image with clipping state
72
+ ctx.drawImage(this._tmpCanvas, 0, 0)
73
+ ctx.restore()
74
+ }
75
+ }
@@ -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,58 @@
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
+ super.apply(target, reltime)
57
+ }
58
+ }