etro 0.7.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/nodejs.yml +4 -2
- package/.github/workflows/shipjs-trigger.yml +29 -0
- package/CHANGELOG.md +73 -12
- package/CODE_OF_CONDUCT.md +5 -5
- package/CONTRIBUTING.md +31 -77
- package/README.md +81 -26
- package/dist/effect/base.d.ts +51 -0
- package/dist/effect/brightness.d.ts +16 -0
- package/dist/effect/channels.d.ts +23 -0
- package/dist/effect/chroma-key.d.ts +23 -0
- package/dist/effect/contrast.d.ts +15 -0
- package/dist/effect/elliptical-mask.d.ts +31 -0
- package/dist/effect/gaussian-blur.d.ts +60 -0
- package/dist/effect/grayscale.d.ts +7 -0
- package/dist/effect/index.d.ts +15 -0
- package/dist/effect/pixelate.d.ts +18 -0
- package/dist/effect/shader.d.ts +99 -0
- package/dist/effect/stack.d.ts +23 -0
- package/dist/effect/transform.d.ts +73 -0
- package/dist/etro-cjs.js +9287 -3331
- package/dist/etro-iife.js +9229 -3273
- package/dist/etro.d.ts +7 -0
- package/dist/event.d.ts +35 -0
- package/dist/index.d.ts +6 -0
- package/dist/layer/audio-source.d.ts +24 -0
- package/dist/layer/audio.d.ts +14 -0
- package/dist/layer/base.d.ts +82 -0
- package/dist/layer/image.d.ts +6 -0
- package/dist/layer/index.d.ts +11 -0
- package/dist/layer/text.d.ts +60 -0
- package/dist/layer/video.d.ts +11 -0
- package/dist/layer/visual-source.d.ts +32 -0
- package/dist/layer/visual.d.ts +58 -0
- package/dist/movie.d.ts +192 -0
- package/dist/object.d.ts +12 -0
- package/dist/util.d.ts +125 -0
- package/eslint.conf.js +2 -9
- package/eslint.example-conf.js +9 -0
- package/eslint.test-conf.js +1 -0
- package/eslint.typescript-conf.js +5 -0
- package/examples/application/readme-screenshot.html +16 -17
- package/examples/application/video-player.html +10 -11
- package/examples/application/webcam.html +6 -6
- package/examples/introduction/audio.html +30 -18
- package/examples/introduction/effects.html +37 -14
- package/examples/introduction/export.html +32 -25
- package/examples/introduction/functions.html +6 -4
- package/examples/introduction/hello-world1.html +9 -5
- package/examples/introduction/hello-world2.html +5 -5
- package/examples/introduction/keyframes.html +35 -23
- package/examples/introduction/media.html +26 -18
- package/examples/introduction/text.html +9 -5
- package/karma.conf.js +5 -3
- package/package.json +36 -14
- package/rollup.config.js +15 -4
- package/scripts/gen-effect-samples.html +26 -25
- package/scripts/save-effect-samples.js +14 -15
- package/ship.config.js +80 -0
- package/src/effect/base.ts +115 -0
- package/src/effect/brightness.ts +43 -0
- package/src/effect/channels.ts +50 -0
- package/src/effect/chroma-key.ts +82 -0
- package/src/effect/contrast.ts +42 -0
- package/src/effect/elliptical-mask.ts +75 -0
- package/src/effect/gaussian-blur.ts +232 -0
- package/src/effect/grayscale.ts +34 -0
- package/src/effect/index.ts +22 -0
- package/src/effect/pixelate.ts +58 -0
- package/src/effect/shader.ts +557 -0
- package/src/effect/stack.ts +78 -0
- package/src/effect/transform.ts +193 -0
- package/src/etro.ts +26 -0
- package/src/event.ts +112 -0
- package/src/index.ts +8 -0
- package/src/layer/audio-source.ts +219 -0
- package/src/layer/audio.ts +34 -0
- package/src/layer/base.ts +175 -0
- package/src/layer/image.ts +8 -0
- package/src/layer/index.ts +13 -0
- package/src/layer/text.ts +138 -0
- package/src/layer/video.ts +15 -0
- package/src/layer/visual-source.ts +150 -0
- package/src/layer/visual.ts +197 -0
- package/src/movie.ts +707 -0
- package/src/object.ts +14 -0
- package/src/util.ts +466 -0
- package/tsconfig.json +8 -0
- package/docs/effect.js.html +0 -1215
- package/docs/event.js.html +0 -145
- package/docs/index.html +0 -81
- package/docs/index.js.html +0 -92
- package/docs/layer.js.html +0 -888
- package/docs/module-effect-GaussianBlurComponent.html +0 -345
- package/docs/module-effect.Brightness.html +0 -339
- package/docs/module-effect.Channels.html +0 -319
- package/docs/module-effect.ChromaKey.html +0 -611
- package/docs/module-effect.Contrast.html +0 -339
- package/docs/module-effect.EllipticalMask.html +0 -200
- package/docs/module-effect.GaussianBlur.html +0 -202
- package/docs/module-effect.GaussianBlurHorizontal.html +0 -242
- package/docs/module-effect.GaussianBlurVertical.html +0 -242
- package/docs/module-effect.Pixelate.html +0 -330
- package/docs/module-effect.Shader.html +0 -1227
- package/docs/module-effect.Stack.html +0 -406
- package/docs/module-effect.Transform.Matrix.html +0 -193
- package/docs/module-effect.Transform.html +0 -1174
- package/docs/module-effect.html +0 -148
- package/docs/module-event.html +0 -473
- package/docs/module-index.html +0 -186
- package/docs/module-layer-Media.html +0 -1116
- package/docs/module-layer-MediaMixin.html +0 -164
- package/docs/module-layer.Audio.html +0 -1188
- package/docs/module-layer.Base.html +0 -629
- package/docs/module-layer.Image.html +0 -1421
- package/docs/module-layer.Text.html +0 -1731
- package/docs/module-layer.Video.html +0 -1938
- package/docs/module-layer.Visual.html +0 -1698
- package/docs/module-layer.html +0 -137
- package/docs/module-movie.html +0 -3118
- package/docs/module-util.Color.html +0 -702
- package/docs/module-util.Font.html +0 -395
- package/docs/module-util.html +0 -845
- package/docs/movie.js.html +0 -689
- package/docs/scripts/collapse.js +0 -20
- package/docs/scripts/linenumber.js +0 -25
- package/docs/scripts/nav.js +0 -12
- package/docs/scripts/polyfill.js +0 -4
- package/docs/scripts/prettify/Apache-License-2.0.txt +0 -202
- package/docs/scripts/prettify/lang-css.js +0 -2
- package/docs/scripts/prettify/prettify.js +0 -28
- package/docs/scripts/search.js +0 -83
- package/docs/styles/jsdoc.css +0 -671
- package/docs/styles/prettify.css +0 -79
- package/docs/util.js.html +0 -503
- package/screenshots/2019-08-17_0.png +0 -0
- package/spec/assets/effect/gaussian-blur-horizontal.png +0 -0
- package/spec/assets/effect/gaussian-blur-vertical.png +0 -0
- package/spec/assets/effect/original.png +0 -0
- package/spec/assets/effect/pixelate.png +0 -0
- package/spec/assets/effect/transform/multiply.png +0 -0
- package/spec/assets/effect/transform/rotate.png +0 -0
- package/spec/assets/effect/transform/scale-fraction.png +0 -0
- package/spec/assets/effect/transform/scale.png +0 -0
- package/spec/assets/effect/transform/translate-fraction.png +0 -0
- package/spec/assets/effect/transform/translate.png +0 -0
- package/spec/assets/layer/audio.wav +0 -0
- package/spec/assets/layer/image.jpg +0 -0
- package/spec/effect.spec.js +0 -352
- package/spec/event.spec.js +0 -25
- package/spec/layer.spec.js +0 -150
- package/spec/movie.spec.js +0 -162
- package/spec/util.spec.js +0 -285
- package/src/effect.js +0 -1268
- package/src/event.js +0 -78
- package/src/index.js +0 -23
- package/src/layer.js +0 -897
- package/src/movie.js +0 -637
- package/src/util.js +0 -505
package/src/object.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Movie } from './movie'
|
|
2
|
+
|
|
3
|
+
/** A movie, layer or effect */
|
|
4
|
+
export default interface EtroObject {
|
|
5
|
+
/** Used in etro internals */
|
|
6
|
+
type: string
|
|
7
|
+
/** Which properties to not watch for changes, for `Movie#autoRefresh` */
|
|
8
|
+
publicExcludes: string[]
|
|
9
|
+
/** Map of property name to function to run on result of `val` */
|
|
10
|
+
propertyFilters: Record<string, <T>(value: T) => T>
|
|
11
|
+
movie: Movie
|
|
12
|
+
|
|
13
|
+
getDefaultOptions(): object // eslint-disable-line @typescript-eslint/ban-types
|
|
14
|
+
}
|
package/src/util.ts
ADDED
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module util
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import EtroObject from './object'
|
|
6
|
+
import { publish } from './event'
|
|
7
|
+
import { Movie } from './movie'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Gets the first matching property descriptor in the prototype chain, or
|
|
11
|
+
* undefined.
|
|
12
|
+
* @param obj
|
|
13
|
+
* @param name
|
|
14
|
+
*/
|
|
15
|
+
function getPropertyDescriptor (obj: unknown, name: string | number | symbol): PropertyDescriptor {
|
|
16
|
+
do {
|
|
17
|
+
const propDesc = Object.getOwnPropertyDescriptor(obj, name)
|
|
18
|
+
if (propDesc)
|
|
19
|
+
return propDesc
|
|
20
|
+
|
|
21
|
+
obj = Object.getPrototypeOf(obj)
|
|
22
|
+
} while (obj)
|
|
23
|
+
return undefined
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Merges `options` with `defaultOptions`, and then copies the properties with
|
|
28
|
+
* the keys in `defaultOptions` from the merged object to `destObj`.
|
|
29
|
+
*
|
|
30
|
+
* @return
|
|
31
|
+
*/
|
|
32
|
+
// TODO: Make methods like getDefaultOptions private
|
|
33
|
+
export function applyOptions (options: object, destObj: EtroObject): void { // eslint-disable-line @typescript-eslint/ban-types
|
|
34
|
+
const defaultOptions = destObj.getDefaultOptions()
|
|
35
|
+
|
|
36
|
+
// Validate; make sure `keys` doesn't have any extraneous items
|
|
37
|
+
for (const option in options)
|
|
38
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
39
|
+
if (!defaultOptions.hasOwnProperty(option))
|
|
40
|
+
throw new Error("Invalid option: '" + option + "'")
|
|
41
|
+
|
|
42
|
+
// Merge options and defaultOptions
|
|
43
|
+
options = { ...defaultOptions, ...options }
|
|
44
|
+
|
|
45
|
+
// Copy options
|
|
46
|
+
for (const option in options) {
|
|
47
|
+
const propDesc = getPropertyDescriptor(destObj, option)
|
|
48
|
+
// Update the property as long as the property has not been set (unless if it has a setter)
|
|
49
|
+
if (!propDesc || propDesc.set)
|
|
50
|
+
destObj[option] = options[option]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// This must be cleared at the start of each frame
|
|
55
|
+
const valCache = new WeakMap()
|
|
56
|
+
function cacheValue (element: EtroObject, path: string, value: unknown) {
|
|
57
|
+
// Initiate movie cache
|
|
58
|
+
if (!valCache.has(element.movie))
|
|
59
|
+
valCache.set(element.movie, new WeakMap())
|
|
60
|
+
|
|
61
|
+
const movieCache = valCache.get(element.movie)
|
|
62
|
+
|
|
63
|
+
// Iniitate element cache
|
|
64
|
+
if (!movieCache.has(element))
|
|
65
|
+
movieCache.set(element, {})
|
|
66
|
+
|
|
67
|
+
const elementCache = movieCache.get(element)
|
|
68
|
+
|
|
69
|
+
// Cache the value
|
|
70
|
+
elementCache[path] = value
|
|
71
|
+
return value
|
|
72
|
+
}
|
|
73
|
+
function hasCachedValue (element, path) {
|
|
74
|
+
return valCache.has(element.movie) &&
|
|
75
|
+
valCache.get(element.movie).has(element) &&
|
|
76
|
+
path in valCache.get(element.movie).get(element)
|
|
77
|
+
}
|
|
78
|
+
function getCachedValue (element, path) {
|
|
79
|
+
return valCache.get(element.movie).get(element)[path]
|
|
80
|
+
}
|
|
81
|
+
export function clearCachedValues (movie: Movie): void {
|
|
82
|
+
valCache.delete(movie)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* A keyframe set.
|
|
87
|
+
*
|
|
88
|
+
* Usage:
|
|
89
|
+
* ```js
|
|
90
|
+
new etro.KeyFrame([time1, value1, interpolation1], [time2, value2])`
|
|
91
|
+
* ```
|
|
92
|
+
* TypeScript users need to specify the type of the value as a type parameter.
|
|
93
|
+
*/
|
|
94
|
+
export class KeyFrame<T> {
|
|
95
|
+
value: unknown[][]
|
|
96
|
+
/** Keys to interpolate, or all keys if undefined */
|
|
97
|
+
interpolationKeys: string[]
|
|
98
|
+
|
|
99
|
+
constructor (...value: T[][]) {
|
|
100
|
+
this.value = value
|
|
101
|
+
this.interpolationKeys = []
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
withKeys (keys: string[]): KeyFrame<T> {
|
|
105
|
+
this.interpolationKeys = keys
|
|
106
|
+
return this
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
evaluate (time: number): T {
|
|
110
|
+
if (this.value.length === 0)
|
|
111
|
+
throw new Error('Empty keyframe')
|
|
112
|
+
|
|
113
|
+
if (time === undefined)
|
|
114
|
+
throw new Error('|time| is undefined or null')
|
|
115
|
+
|
|
116
|
+
const firstTime: number = this.value[0][0] as number
|
|
117
|
+
if (time < firstTime)
|
|
118
|
+
throw new Error('No keyframe point before |time|')
|
|
119
|
+
|
|
120
|
+
// I think reduce are slow to do per-frame (or more)?
|
|
121
|
+
for (let i = 0; i < this.value.length; i++) {
|
|
122
|
+
const startTime = this.value[i][0] as number
|
|
123
|
+
const startValue = this.value[i][1] as T
|
|
124
|
+
type interpolateType = <U = number | object>(startValue: U, endValue: U, percentProgress: number, interpolationKeys: string[]) => U // eslint-disable-line @typescript-eslint/ban-types
|
|
125
|
+
const interpolate = this.value[i].length === 3 ? this.value[i][2] as interpolateType : linearInterp
|
|
126
|
+
if (i + 1 < this.value.length) {
|
|
127
|
+
const endTime = this.value[i + 1][0] as number
|
|
128
|
+
const endValue = this.value[i + 1][1] as T
|
|
129
|
+
if (startTime <= time && time < endTime)
|
|
130
|
+
// No need for endValue if it is flat interpolation
|
|
131
|
+
// TODO: support custom interpolation for 'other' types?
|
|
132
|
+
if (!(typeof startValue === 'number' || typeof endValue === 'object')) {
|
|
133
|
+
return startValue
|
|
134
|
+
} else if (typeof startValue !== typeof endValue) {
|
|
135
|
+
throw new Error('Type mismatch in keyframe values')
|
|
136
|
+
} else {
|
|
137
|
+
// Interpolate
|
|
138
|
+
const percentProgress = (time - startTime) / (endTime - startTime)
|
|
139
|
+
return interpolate(
|
|
140
|
+
startValue as unknown as (number | object), // eslint-disable-line @typescript-eslint/ban-types
|
|
141
|
+
endValue as unknown as (number | object), // eslint-disable-line @typescript-eslint/ban-types
|
|
142
|
+
percentProgress, this.interpolationKeys
|
|
143
|
+
) as unknown as T
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
// Repeat last value forever
|
|
147
|
+
return startValue
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** A dynamic property. Supports simple values, keyframes and functions */
|
|
154
|
+
export type Dynamic<T> = T | KeyFrame<T> | ((element: EtroObject, time: number) => T)
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Computes a property.
|
|
158
|
+
*
|
|
159
|
+
* @param element - the etro object to which the property belongs to
|
|
160
|
+
* @param path - the dot-separated path to a property on `element`
|
|
161
|
+
* @param time - time to calculate keyframes for, if necessary
|
|
162
|
+
*
|
|
163
|
+
* Note that only values used in keyframes that are numbers or objects
|
|
164
|
+
* (including arrays) are interpolated. All other values are taken sequentially
|
|
165
|
+
* with no interpolation. JavaScript will convert parsed colors, if created
|
|
166
|
+
* correctly, to their string representations when assigned to a
|
|
167
|
+
* CanvasRenderingContext2D property.
|
|
168
|
+
*/
|
|
169
|
+
// TODO: Is this function efficient?
|
|
170
|
+
// TODO: Update doc @params to allow for keyframes
|
|
171
|
+
export function val (element: EtroObject, path: string, time: number): any { // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
172
|
+
if (hasCachedValue(element, path))
|
|
173
|
+
return getCachedValue(element, path)
|
|
174
|
+
|
|
175
|
+
// Get property of element at path
|
|
176
|
+
const pathParts = path.split('.')
|
|
177
|
+
let property = element[pathParts.shift()]
|
|
178
|
+
while (pathParts.length > 0)
|
|
179
|
+
property = property[pathParts.shift()]
|
|
180
|
+
|
|
181
|
+
// Property filter function
|
|
182
|
+
const process = element.propertyFilters[path]
|
|
183
|
+
|
|
184
|
+
let value
|
|
185
|
+
if (property instanceof KeyFrame)
|
|
186
|
+
value = property.evaluate(time)
|
|
187
|
+
else if (typeof property === 'function')
|
|
188
|
+
value = property(element, time) // TODO? add more args
|
|
189
|
+
else
|
|
190
|
+
// Simple value
|
|
191
|
+
value = property
|
|
192
|
+
|
|
193
|
+
return cacheValue(element, path, process ? process.call(element, value) : value)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* export function floorInterp(x1, x2, t, objectKeys) {
|
|
197
|
+
// https://stackoverflow.com/a/25835337/3783155 (TODO: preserve getters/setters, etc?)
|
|
198
|
+
return !objectKeys ? x1 : objectKeys.reduce((a, x) => {
|
|
199
|
+
if (x1.hasOwnProperty(x)) a[x] = o[x]; // ignore x2
|
|
200
|
+
return a;
|
|
201
|
+
}, Object.create(Object.getPrototypeOf(x1)));
|
|
202
|
+
} */
|
|
203
|
+
|
|
204
|
+
export function linearInterp (x1: number | object, x2: number | object, t: number, objectKeys?: string[]): number | object { // eslint-disable-line @typescript-eslint/ban-types
|
|
205
|
+
if (typeof x1 !== typeof x2)
|
|
206
|
+
throw new Error('Type mismatch')
|
|
207
|
+
|
|
208
|
+
if (typeof x1 !== 'number' && typeof x1 !== 'object')
|
|
209
|
+
// Flat interpolation (floor)
|
|
210
|
+
return x1
|
|
211
|
+
|
|
212
|
+
if (typeof x1 === 'object') { // to work with objects (including arrays)
|
|
213
|
+
// TODO: make this code DRY
|
|
214
|
+
if (Object.getPrototypeOf(x1) !== Object.getPrototypeOf(x2))
|
|
215
|
+
throw new Error('Prototype mismatch')
|
|
216
|
+
|
|
217
|
+
// Preserve prototype of objects
|
|
218
|
+
const int = Object.create(Object.getPrototypeOf(x1))
|
|
219
|
+
// Take the intersection of properties
|
|
220
|
+
const keys = Object.keys(x1) || objectKeys // TODO: reverse operands
|
|
221
|
+
for (let i = 0; i < keys.length; i++) {
|
|
222
|
+
const key = keys[i]
|
|
223
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
224
|
+
if (!x1.hasOwnProperty(key) || !x2.hasOwnProperty(key))
|
|
225
|
+
continue
|
|
226
|
+
|
|
227
|
+
int[key] = linearInterp(x1[key], x2[key], t)
|
|
228
|
+
}
|
|
229
|
+
return int
|
|
230
|
+
}
|
|
231
|
+
return (1 - t) * x1 + t * (x2 as number)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function cosineInterp (x1: number | object, x2: number | object, t: number, objectKeys?: string[]): number | object { // eslint-disable-line @typescript-eslint/ban-types
|
|
235
|
+
if (typeof x1 !== typeof x2)
|
|
236
|
+
throw new Error('Type mismatch')
|
|
237
|
+
|
|
238
|
+
if (typeof x1 !== 'number' && typeof x1 !== 'object')
|
|
239
|
+
// Flat interpolation (floor)
|
|
240
|
+
return x1
|
|
241
|
+
|
|
242
|
+
if (typeof x1 === 'object' && typeof x2 === 'object') { // to work with objects (including arrays)
|
|
243
|
+
if (Object.getPrototypeOf(x1) !== Object.getPrototypeOf(x2))
|
|
244
|
+
throw new Error('Prototype mismatch')
|
|
245
|
+
|
|
246
|
+
// Preserve prototype of objects
|
|
247
|
+
const int = Object.create(Object.getPrototypeOf(x1))
|
|
248
|
+
// Take the intersection of properties
|
|
249
|
+
const keys = Object.keys(x1) || objectKeys
|
|
250
|
+
for (let i = 0; i < keys.length; i++) {
|
|
251
|
+
const key = keys[i]
|
|
252
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
253
|
+
if (!x1.hasOwnProperty(key) || !x2.hasOwnProperty(key))
|
|
254
|
+
continue
|
|
255
|
+
|
|
256
|
+
int[key] = cosineInterp(x1[key], x2[key], t)
|
|
257
|
+
}
|
|
258
|
+
return int
|
|
259
|
+
}
|
|
260
|
+
const cos = Math.cos(Math.PI / 2 * t)
|
|
261
|
+
return cos * (x1 as number) + (1 - cos) * (x2 as number)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* An RGBA color, for proper interpolation and shader effects
|
|
266
|
+
*/
|
|
267
|
+
export class Color {
|
|
268
|
+
r: number
|
|
269
|
+
g: number
|
|
270
|
+
b: number
|
|
271
|
+
a: number
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* @param r
|
|
275
|
+
* @param g
|
|
276
|
+
* @param b
|
|
277
|
+
* @param a
|
|
278
|
+
*/
|
|
279
|
+
constructor (r: number, g: number, b: number, a = 1.0) {
|
|
280
|
+
this.r = r
|
|
281
|
+
this.g = g
|
|
282
|
+
this.b = b
|
|
283
|
+
this.a = a
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Converts to a CSS color
|
|
288
|
+
*/
|
|
289
|
+
toString (): string {
|
|
290
|
+
return `rgba(${this.r}, ${this.g}, ${this.b}, ${this.a})`
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const parseColorCanvas = document.createElement('canvas')
|
|
295
|
+
parseColorCanvas.width = parseColorCanvas.height = 1
|
|
296
|
+
const parseColorCtx = parseColorCanvas.getContext('2d')
|
|
297
|
+
/**
|
|
298
|
+
* Converts a CSS color string to a {@link Color} object representation.
|
|
299
|
+
* @param str
|
|
300
|
+
* @return the parsed color
|
|
301
|
+
*/
|
|
302
|
+
export function parseColor (str: string): Color {
|
|
303
|
+
// TODO - find a better way to deal with the fact that invalid values of "col"
|
|
304
|
+
// are ignored.
|
|
305
|
+
parseColorCtx.clearRect(0, 0, 1, 1)
|
|
306
|
+
parseColorCtx.fillStyle = str
|
|
307
|
+
parseColorCtx.fillRect(0, 0, 1, 1)
|
|
308
|
+
const data = parseColorCtx.getImageData(0, 0, 1, 1).data
|
|
309
|
+
return new Color(data[0], data[1], data[2], data[3] / 255)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* A font, for proper interpolation
|
|
314
|
+
*/
|
|
315
|
+
export class Font {
|
|
316
|
+
size: number
|
|
317
|
+
sizeUnit: string
|
|
318
|
+
family: string
|
|
319
|
+
style: string
|
|
320
|
+
variant: string
|
|
321
|
+
weight: string
|
|
322
|
+
stretch: string
|
|
323
|
+
lineHeight: string
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* @param size
|
|
327
|
+
* @param family
|
|
328
|
+
* @param sizeUnit
|
|
329
|
+
*/
|
|
330
|
+
constructor (size: number, sizeUnit: string, family: string, style = 'normal', variant = 'normal',
|
|
331
|
+
weight = 'normal', stretch = 'normal', lineHeight = 'normal') {
|
|
332
|
+
this.size = size
|
|
333
|
+
this.sizeUnit = sizeUnit
|
|
334
|
+
this.family = family
|
|
335
|
+
this.style = style
|
|
336
|
+
this.variant = variant
|
|
337
|
+
this.weight = weight
|
|
338
|
+
this.stretch = stretch
|
|
339
|
+
this.lineHeight = lineHeight
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Converts to CSS font syntax
|
|
344
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/font
|
|
345
|
+
*/
|
|
346
|
+
toString (): string {
|
|
347
|
+
let s = ''
|
|
348
|
+
if (this.style !== 'normal') s += this.style + ' '
|
|
349
|
+
if (this.variant !== 'normal') s += this.variant + ' '
|
|
350
|
+
if (this.weight !== 'normal') s += this.weight + ' '
|
|
351
|
+
if (this.stretch !== 'normal') s += this.stretch + ' '
|
|
352
|
+
s += `${this.size}${this.sizeUnit} `
|
|
353
|
+
if (this.lineHeight !== 'normal') s += this.lineHeight + ' '
|
|
354
|
+
s += this.family
|
|
355
|
+
|
|
356
|
+
return s
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const parseFontEl = document.createElement('div')
|
|
361
|
+
/**
|
|
362
|
+
* Converts a CSS font string to a {@link Font} object
|
|
363
|
+
* representation.
|
|
364
|
+
* @param str
|
|
365
|
+
* @return the parsed font
|
|
366
|
+
*/
|
|
367
|
+
export function parseFont (str: string): Font {
|
|
368
|
+
// Assign css string to html element
|
|
369
|
+
parseFontEl.setAttribute('style', `font: ${str}`)
|
|
370
|
+
const {
|
|
371
|
+
fontSize, fontFamily, fontStyle, fontVariant, fontWeight, lineHeight
|
|
372
|
+
} = parseFontEl.style
|
|
373
|
+
parseFontEl.removeAttribute('style')
|
|
374
|
+
|
|
375
|
+
const size = parseFloat(fontSize)
|
|
376
|
+
const sizeUnit = fontSize.substring(size.toString().length)
|
|
377
|
+
return new Font(size, sizeUnit, fontFamily, fontStyle, fontVariant, fontWeight, lineHeight)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* @param mapper
|
|
382
|
+
* @param canvas
|
|
383
|
+
* @param ctx
|
|
384
|
+
* @param x
|
|
385
|
+
* @param y
|
|
386
|
+
* @param width
|
|
387
|
+
* @param height
|
|
388
|
+
* @param flush
|
|
389
|
+
* @deprecated Use {@link effect.Shader} instead
|
|
390
|
+
*/
|
|
391
|
+
export function mapPixels (
|
|
392
|
+
mapper: (pixels: Uint8ClampedArray, i: number) => void,
|
|
393
|
+
canvas: HTMLCanvasElement,
|
|
394
|
+
ctx: CanvasRenderingContext2D,
|
|
395
|
+
x: number,
|
|
396
|
+
y: number,
|
|
397
|
+
width: number,
|
|
398
|
+
height: number,
|
|
399
|
+
flush = true
|
|
400
|
+
): void {
|
|
401
|
+
x = x || 0
|
|
402
|
+
y = y || 0
|
|
403
|
+
width = width || canvas.width
|
|
404
|
+
height = height || canvas.height
|
|
405
|
+
const frame = ctx.getImageData(x, y, width, height)
|
|
406
|
+
for (let i = 0, l = frame.data.length; i < l; i += 4)
|
|
407
|
+
mapper(frame.data, i)
|
|
408
|
+
|
|
409
|
+
if (flush)
|
|
410
|
+
ctx.putImageData(frame, x, y)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* <p>Emits "change" event when public properties updated, recursively.
|
|
415
|
+
* <p>Must be called before any watchable properties are set, and only once in
|
|
416
|
+
* the prototype chain.
|
|
417
|
+
*
|
|
418
|
+
* @param target - object to watch
|
|
419
|
+
*/
|
|
420
|
+
export function watchPublic (target: EtroObject): EtroObject {
|
|
421
|
+
const getPath = (receiver, prop) =>
|
|
422
|
+
(receiver === proxy ? '' : (paths.get(receiver) + '.')) + prop
|
|
423
|
+
const callback = function (prop, val, receiver) {
|
|
424
|
+
// Public API property updated, emit 'modify' event.
|
|
425
|
+
publish(proxy, `${target.type}.change.modify`, { property: getPath(receiver, prop), newValue: val })
|
|
426
|
+
}
|
|
427
|
+
const canWatch = (receiver, prop) => !prop.startsWith('_') &&
|
|
428
|
+
(receiver.publicExcludes === undefined || !receiver.publicExcludes.includes(prop))
|
|
429
|
+
|
|
430
|
+
// The path to each child property (each is a unique proxy)
|
|
431
|
+
const paths = new WeakMap()
|
|
432
|
+
|
|
433
|
+
const handler = {
|
|
434
|
+
set (obj, prop, val, receiver) {
|
|
435
|
+
// Recurse
|
|
436
|
+
if (typeof val === 'object' && val !== null && !paths.has(val) && canWatch(receiver, prop)) {
|
|
437
|
+
val = new Proxy(val, handler)
|
|
438
|
+
paths.set(val, getPath(receiver, prop))
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Set property or attribute
|
|
442
|
+
// Search prototype chain for the closest setter
|
|
443
|
+
let objProto = obj
|
|
444
|
+
while ((objProto = Object.getPrototypeOf(objProto))) {
|
|
445
|
+
const propDesc = Object.getOwnPropertyDescriptor(objProto, prop)
|
|
446
|
+
if (propDesc && propDesc.set) {
|
|
447
|
+
// Call setter, supplying proxy as this (fixes event bugs)
|
|
448
|
+
propDesc.set.call(receiver, val)
|
|
449
|
+
break
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (!objProto)
|
|
453
|
+
// Couldn't find setter; set value on instance
|
|
454
|
+
obj[prop] = val
|
|
455
|
+
|
|
456
|
+
// Check if the property isn't blacklisted in publicExcludes.
|
|
457
|
+
if (canWatch(receiver, prop))
|
|
458
|
+
callback(prop, val, receiver)
|
|
459
|
+
|
|
460
|
+
return true
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const proxy = new Proxy(target, handler)
|
|
465
|
+
return proxy
|
|
466
|
+
}
|