fragment-tools 0.2.12 → 0.2.14
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/package.json +12 -11
- package/src/cli/build.js +1 -0
- package/src/cli/create.js +22 -4
- package/src/cli/createConfig.js +8 -3
- package/src/cli/getEntries.js +10 -1
- package/src/cli/plugins/hot-shader-replacement.js +54 -16
- package/src/cli/plugins/save.js +97 -38
- package/src/cli/prompts.js +89 -36
- package/src/cli/run.js +1 -1
- package/src/client/app/actions/resize.js +8 -1
- package/src/client/app/attachments/draggable.js +93 -0
- package/src/client/app/client.js +90 -18
- package/src/client/app/components/IconFlip.svelte +46 -0
- package/src/client/app/hooks.js +25 -1
- package/src/client/app/lib/canvas-recorder/CanvasRecorder.js +95 -3
- package/src/client/app/lib/canvas-recorder/FrameRecorder.js +45 -3
- package/src/client/app/lib/canvas-recorder/GIFRecorder.js +72 -13
- package/src/client/app/lib/canvas-recorder/MediaBunnyRecorder.js +43 -9
- package/src/client/app/lib/canvas-recorder/utils.js +18 -9
- package/src/client/app/renderers/2DRenderer.js +20 -16
- package/src/client/app/renderers/FragmentRenderer.js +1 -1
- package/src/client/app/renderers/P5GLRenderer.js +14 -6
- package/src/client/app/renderers/P5Renderer.js +9 -1
- package/src/client/app/renderers/THREERenderer.js +62 -48
- package/src/client/app/state/Sketch.svelte.js +149 -9
- package/src/client/app/state/errors.svelte.js +19 -0
- package/src/client/app/state/exports.svelte.js +14 -1
- package/src/client/app/state/rendering.svelte.js +47 -0
- package/src/client/app/state/sketches.svelte.js +43 -7
- package/src/client/app/state/utils.svelte.js +49 -0
- package/src/client/app/ui/Field.svelte +6 -1
- package/src/client/app/ui/FieldSection.svelte +4 -4
- package/src/client/app/ui/ParamsOutput.svelte +1 -1
- package/src/client/app/ui/SketchRenderer.svelte +16 -0
- package/src/client/app/ui/fields/ButtonInput.svelte +2 -0
- package/src/client/app/ui/fields/CheckboxInput.svelte +13 -11
- package/src/client/app/ui/fields/ColorInput.svelte +16 -11
- package/src/client/app/ui/fields/GradientInput.svelte +607 -0
- package/src/client/app/ui/fields/Input.svelte +10 -6
- package/src/client/app/ui/fields/IntervalInput.svelte +27 -35
- package/src/client/app/ui/fields/NumberInput.svelte +51 -13
- package/src/client/app/ui/fields/PaletteInput.svelte +181 -0
- package/src/client/app/ui/fields/ProgressInput.svelte +44 -16
- package/src/client/app/ui/fields/TextareaInput.svelte +10 -10
- package/src/client/app/utils/canvas.utils.js +105 -28
- package/src/client/app/utils/color.utils.js +74 -17
- package/src/client/app/utils/fields.utils.js +68 -26
- package/src/client/app/utils/file.utils.js +86 -31
- package/src/client/app/utils/glsl.utils.js +11 -2
- package/src/client/app/utils/glslErrors.js +31 -21
- package/src/client/app/utils/index.js +28 -12
- package/src/client/main.js +7 -1
- package/src/types/global.d.ts +143 -0
- package/src/types/props.d.ts +40 -15
- package/tsconfig.json +1 -1
|
@@ -1,6 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @callback CanvasRecorderStartCallback
|
|
3
|
+
* @returns {void}
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @callback CanvasRecorderTickCallback
|
|
8
|
+
* @param {TickData} data - Tick data
|
|
9
|
+
* @returns {void}
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @callback CanvasRecorderCompleteCallback
|
|
14
|
+
* @param {Blob | Blob[] | null} result
|
|
15
|
+
* @returns {void}
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {Object} TickData
|
|
20
|
+
* @property {number} time - Current time in milliseconds
|
|
21
|
+
* @property {number} deltaTime - Time since last frame in milliseconds
|
|
22
|
+
* @property {number} frameCount - Current frame count
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {Object} CanvasRecorderOptions
|
|
27
|
+
* @property {number} [duration=Infinity] - Recording duration in seconds
|
|
28
|
+
* @property {number} [framerate=25] - Frames per second
|
|
29
|
+
* @property {number} [quality=100] - Recording quality (1-100)
|
|
30
|
+
* @property {string} format - Output format
|
|
31
|
+
* @property {CanvasRecorderStartCallback} [onStart] - Callback when recording starts
|
|
32
|
+
* @property {CanvasRecorderTickCallback} [onTick] - Callback on each frame
|
|
33
|
+
* @property {CanvasRecorderCompleteCallback} [onComplete] - Callback when recording completes
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/** @type {CanvasRecorderStartCallback} */
|
|
1
37
|
let noop = () => {};
|
|
2
38
|
|
|
3
39
|
class CanvasRecorder {
|
|
40
|
+
/**
|
|
41
|
+
* Create a canvas recorder
|
|
42
|
+
* @param {HTMLCanvasElement} canvas - The canvas to record
|
|
43
|
+
* @param {CanvasRecorderOptions} options - Recording options
|
|
44
|
+
*/
|
|
4
45
|
constructor(
|
|
5
46
|
canvas,
|
|
6
47
|
{
|
|
@@ -13,30 +54,62 @@ class CanvasRecorder {
|
|
|
13
54
|
onComplete = noop,
|
|
14
55
|
},
|
|
15
56
|
) {
|
|
57
|
+
/** @type {HTMLCanvasElement} */
|
|
16
58
|
this.canvas = canvas;
|
|
59
|
+
/** @type {number} */
|
|
17
60
|
this.framerate = framerate;
|
|
61
|
+
/** @type {number} */
|
|
18
62
|
this.duration = duration;
|
|
63
|
+
/** @type {number} */
|
|
19
64
|
this.quality = quality;
|
|
65
|
+
/** @type {string} */
|
|
20
66
|
this.format = format;
|
|
67
|
+
/** @type {CanvasRecorderStartCallback} */
|
|
21
68
|
this.onStart = onStart;
|
|
69
|
+
/** @type {CanvasRecorderTickCallback} */
|
|
22
70
|
this.onTick = onTick;
|
|
71
|
+
/** @type {Function} */
|
|
23
72
|
this.onComplete = onComplete;
|
|
24
|
-
|
|
73
|
+
/** @type {number} */
|
|
25
74
|
this.time = 0;
|
|
75
|
+
|
|
76
|
+
/** @type {number} */
|
|
26
77
|
this.deltaTime = 1000 / this.framerate;
|
|
27
78
|
|
|
79
|
+
/** @type {number} */
|
|
28
80
|
this.frameDuration = 1000 / this.framerate;
|
|
81
|
+
|
|
82
|
+
/** @type {number} */
|
|
29
83
|
this.frameTotal = isFinite(this.duration)
|
|
30
84
|
? this.duration * this.framerate
|
|
31
85
|
: Infinity;
|
|
86
|
+
|
|
87
|
+
/** @type {boolean} */
|
|
32
88
|
this.started = false;
|
|
89
|
+
|
|
90
|
+
/** @type {boolean} */
|
|
33
91
|
this.stopped = false;
|
|
34
92
|
|
|
93
|
+
/** @type {number} */
|
|
35
94
|
this.startTime = 0;
|
|
95
|
+
|
|
96
|
+
/** @type {number} */
|
|
97
|
+
this.frameCount = 0;
|
|
98
|
+
|
|
99
|
+
/** @type {Blob | Blob[] | null} */
|
|
100
|
+
this.result = null;
|
|
36
101
|
}
|
|
37
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Load resources before recording (override in subclass)
|
|
105
|
+
* @returns {Promise<void>}
|
|
106
|
+
*/
|
|
38
107
|
async load() {}
|
|
39
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Start the recording
|
|
111
|
+
* @returns {Promise<void>}
|
|
112
|
+
*/
|
|
40
113
|
async start() {
|
|
41
114
|
this.startTime = performance.now();
|
|
42
115
|
this.onStart();
|
|
@@ -65,11 +138,17 @@ class CanvasRecorder {
|
|
|
65
138
|
this._tick();
|
|
66
139
|
}
|
|
67
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Internal tick handler
|
|
143
|
+
* @private
|
|
144
|
+
* @returns {Promise<void>}
|
|
145
|
+
*/
|
|
68
146
|
async _tick() {
|
|
69
147
|
console.log(`CanvasRecorder - render frame ${this.frameCount + 1}`);
|
|
70
148
|
this.onTick({
|
|
71
149
|
time: this.time,
|
|
72
150
|
deltaTime: this.deltaTime,
|
|
151
|
+
frameCount: this.frameCount,
|
|
73
152
|
});
|
|
74
153
|
|
|
75
154
|
await this.tick({
|
|
@@ -98,8 +177,17 @@ class CanvasRecorder {
|
|
|
98
177
|
}
|
|
99
178
|
}
|
|
100
179
|
|
|
101
|
-
|
|
102
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Process a single frame (override in subclass)
|
|
182
|
+
* @param {TickData} _data - Frame data
|
|
183
|
+
* @returns {Promise<void>}
|
|
184
|
+
*/
|
|
185
|
+
async tick(_data) {}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* End the recording and compile result
|
|
189
|
+
* @returns {void}
|
|
190
|
+
*/
|
|
103
191
|
end() {
|
|
104
192
|
console.log(
|
|
105
193
|
`CanvasRecorder - compiled ${this.frameCount + 1} frames in ${(performance.now() - this.startTime) / 1000}s`,
|
|
@@ -107,6 +195,10 @@ class CanvasRecorder {
|
|
|
107
195
|
this.onComplete(this.result);
|
|
108
196
|
}
|
|
109
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Stop the recording
|
|
200
|
+
* @returns {void}
|
|
201
|
+
*/
|
|
110
202
|
stop() {
|
|
111
203
|
this.stopped = true;
|
|
112
204
|
}
|
|
@@ -3,24 +3,62 @@ import { map } from '../../utils/math.utils';
|
|
|
3
3
|
import CanvasRecorder from './CanvasRecorder';
|
|
4
4
|
import { exportCanvas } from './utils';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {Object} FrameRecorderOptions
|
|
8
|
+
* @property {string} [imageEncoding='png'] - Image encoding format (png, jpeg, webp)
|
|
9
|
+
* @property {number} [duration] - Recording duration in seconds
|
|
10
|
+
* @property {number} [framerate] - Frames per second
|
|
11
|
+
* @property {number} [quality] - Recording quality (1-100)
|
|
12
|
+
* @property {string} format - Output format
|
|
13
|
+
* @property {import('./CanvasRecorder').CanvasRecorderStartCallback} [onStart] - Callback when recording starts
|
|
14
|
+
* @property {import('./CanvasRecorder').CanvasRecorderTickCallback} [onTick] - Callback on each frame
|
|
15
|
+
* @property {import('./CanvasRecorder').CanvasRecorderCompleteCallback} [onComplete] - Callback when recording completes
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Recorder that captures individual frames as images
|
|
20
|
+
* @extends CanvasRecorder
|
|
21
|
+
*/
|
|
6
22
|
class FrameRecorder extends CanvasRecorder {
|
|
23
|
+
/**
|
|
24
|
+
* Create a frame recorder
|
|
25
|
+
* @param {HTMLCanvasElement} canvas - The canvas to record
|
|
26
|
+
* @param {FrameRecorderOptions} options - Recording options
|
|
27
|
+
*/
|
|
7
28
|
constructor(canvas, options) {
|
|
8
29
|
super(canvas, options);
|
|
9
30
|
|
|
10
31
|
const { imageEncoding = 'png' } = options;
|
|
11
32
|
|
|
33
|
+
/** @type {string} */
|
|
12
34
|
this.imageEncoding = imageEncoding;
|
|
13
35
|
|
|
36
|
+
/** @type {number} */
|
|
14
37
|
this.imageQuality = map(this.quality, 1, 100, 0, 1);
|
|
38
|
+
|
|
39
|
+
/** @type {string[]} */
|
|
40
|
+
this.frames = [];
|
|
41
|
+
|
|
42
|
+
/** @type {Blob[]} */
|
|
43
|
+
this.result = [];
|
|
15
44
|
}
|
|
16
45
|
|
|
17
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Start recording frames
|
|
48
|
+
* @returns {Promise<void>}
|
|
49
|
+
*/
|
|
50
|
+
async start() {
|
|
18
51
|
this.frames = [];
|
|
19
52
|
|
|
20
|
-
super.start();
|
|
53
|
+
await super.start();
|
|
21
54
|
}
|
|
22
55
|
|
|
23
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Capture a single frame
|
|
58
|
+
* @param {import('./CanvasRecorder').TickData} _data - Frame data (unused)
|
|
59
|
+
* @returns {Promise<void>}
|
|
60
|
+
*/
|
|
61
|
+
async tick(_data) {
|
|
24
62
|
let { dataURL } = exportCanvas(this.canvas, {
|
|
25
63
|
encoding: `image/${this.imageEncoding}`,
|
|
26
64
|
encodingQuality: this.imageQuality,
|
|
@@ -29,6 +67,10 @@ class FrameRecorder extends CanvasRecorder {
|
|
|
29
67
|
this.frames[this.frameCount] = dataURL;
|
|
30
68
|
}
|
|
31
69
|
|
|
70
|
+
/**
|
|
71
|
+
* End recording and convert frames to blobs
|
|
72
|
+
* @returns {Promise<void>}
|
|
73
|
+
*/
|
|
32
74
|
async end() {
|
|
33
75
|
this.result = await Promise.all(
|
|
34
76
|
this.frames.map((dataURL) => createBlobFromDataURL(dataURL)),
|
|
@@ -2,8 +2,41 @@ import { map } from '../../utils/math.utils';
|
|
|
2
2
|
import { GIFEncoder, quantize, applyPalette } from 'gifenc';
|
|
3
3
|
import CanvasRecorder from './CanvasRecorder';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {import('./CanvasRecorder').CanvasRecorderOptions} GIFRecorderOptions
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Recorder that captures frames and encodes them as an animated GIF
|
|
11
|
+
* @extends CanvasRecorder
|
|
12
|
+
*/
|
|
5
13
|
class GIFRecorder extends CanvasRecorder {
|
|
6
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Create a GIF recorder
|
|
16
|
+
* @param {HTMLCanvasElement} canvas - The canvas to record
|
|
17
|
+
* @param {GIFRecorderOptions} options - Recording options
|
|
18
|
+
*/
|
|
19
|
+
constructor(canvas, options) {
|
|
20
|
+
super(canvas, options);
|
|
21
|
+
|
|
22
|
+
/** @type {ReturnType<typeof GIFEncoder> | null} */
|
|
23
|
+
this.encoder = null;
|
|
24
|
+
|
|
25
|
+
/** @type {HTMLCanvasElement} */
|
|
26
|
+
this.tmpCanvas = document.createElement('canvas');
|
|
27
|
+
|
|
28
|
+
/** @type {CanvasRenderingContext2D | null} */
|
|
29
|
+
this.tmpContext = this.tmpCanvas.getContext('2d');
|
|
30
|
+
|
|
31
|
+
/** @type {number} */
|
|
32
|
+
this.maxColors = 256;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Start GIF recording
|
|
37
|
+
* @returns {Promise<void>}
|
|
38
|
+
*/
|
|
39
|
+
async start() {
|
|
7
40
|
this.encoder = GIFEncoder();
|
|
8
41
|
|
|
9
42
|
this.tmpCanvas = document.createElement('canvas');
|
|
@@ -21,34 +54,60 @@ class GIFRecorder extends CanvasRecorder {
|
|
|
21
54
|
: Infinity;
|
|
22
55
|
}
|
|
23
56
|
|
|
24
|
-
super.start();
|
|
57
|
+
await super.start();
|
|
25
58
|
}
|
|
26
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Get RGBA pixel data from a bitmap
|
|
62
|
+
* @param {HTMLCanvasElement | ImageBitmap} bitmap - The bitmap to extract pixels from
|
|
63
|
+
* @param {number} [width=bitmap.width] - Target width
|
|
64
|
+
* @param {number} [height=bitmap.height] - Target height
|
|
65
|
+
* @returns {Uint8ClampedArray} RGBA pixel data
|
|
66
|
+
*/
|
|
27
67
|
getBitmapRGBA(bitmap, width = bitmap.width, height = bitmap.height) {
|
|
28
68
|
this.tmpCanvas.width = width;
|
|
29
69
|
this.tmpCanvas.height = height;
|
|
30
|
-
|
|
31
|
-
this.tmpContext
|
|
32
|
-
|
|
70
|
+
|
|
71
|
+
if (this.tmpContext) {
|
|
72
|
+
this.tmpContext.clearRect(0, 0, width, height);
|
|
73
|
+
this.tmpContext.drawImage(bitmap, 0, 0, width, height);
|
|
74
|
+
return this.tmpContext.getImageData(0, 0, width, height).data;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return new Uint8ClampedArray();
|
|
33
78
|
}
|
|
34
79
|
|
|
35
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Capture and encode a single frame
|
|
82
|
+
* @param {import('./CanvasRecorder').TickData} _data - Frame data (unused)
|
|
83
|
+
* @returns {Promise<void>}
|
|
84
|
+
*/
|
|
85
|
+
async tick(_data) {
|
|
36
86
|
const { width, height } = this.canvas;
|
|
37
87
|
|
|
38
88
|
const pixels = this.getBitmapRGBA(this.canvas, width, height);
|
|
39
89
|
const palette = quantize(pixels, this.maxColors);
|
|
40
90
|
const index = applyPalette(pixels, palette);
|
|
41
91
|
|
|
42
|
-
this.encoder
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
92
|
+
if (this.encoder) {
|
|
93
|
+
this.encoder.writeFrame(index, width, height, {
|
|
94
|
+
palette: palette,
|
|
95
|
+
delay: this.frameDuration,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
46
98
|
}
|
|
47
99
|
|
|
100
|
+
/**
|
|
101
|
+
* End recording and create GIF blob
|
|
102
|
+
* @returns {void}
|
|
103
|
+
*/
|
|
48
104
|
end() {
|
|
49
|
-
this.encoder
|
|
50
|
-
|
|
51
|
-
|
|
105
|
+
if (this.encoder) {
|
|
106
|
+
this.encoder.finish();
|
|
107
|
+
this.result = new Blob([this.encoder.bytes()], {
|
|
108
|
+
type: 'image/gif',
|
|
109
|
+
});
|
|
110
|
+
}
|
|
52
111
|
|
|
53
112
|
super.end();
|
|
54
113
|
}
|
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
Mp4OutputFormat,
|
|
5
5
|
MkvOutputFormat,
|
|
6
6
|
MovOutputFormat,
|
|
7
|
-
WebMInputFormat,
|
|
8
7
|
BufferTarget,
|
|
9
8
|
CanvasSource,
|
|
10
9
|
Quality,
|
|
@@ -18,6 +17,26 @@ import {
|
|
|
18
17
|
import { map } from '@fragment/utils/math.utils.js';
|
|
19
18
|
import { VIDEO_FORMATS } from '@fragment/state/exports.svelte.js';
|
|
20
19
|
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {'avc' | 'hevc' | 'vp9' | 'av1' | 'vp8'} VideoCodec
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {Object} MediaBunnyRecorderOptions
|
|
26
|
+
* @property {VideoCodec} codec - Video codec to use
|
|
27
|
+
* @property {number} [duration] - Recording duration in seconds
|
|
28
|
+
* @property {number} [framerate] - Frames per second
|
|
29
|
+
* @property {number} [quality] - Recording quality (1-100)
|
|
30
|
+
* @property {string} format - Output format
|
|
31
|
+
* @property {import('./CanvasRecorder').CanvasRecorderStartCallback} [onStart] - Callback when recording starts
|
|
32
|
+
* @property {import('./CanvasRecorder').CanvasRecorderTickCallback} [onTick] - Callback on each frame
|
|
33
|
+
* @property {import('./CanvasRecorder').CanvasRecorderCompleteCallback} [onComplete] - Callback when recording completes
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Recorder that uses MediaBunny to encode video
|
|
38
|
+
* @extends CanvasRecorder
|
|
39
|
+
*/
|
|
21
40
|
class MediaBunnyRecorder extends CanvasRecorder {
|
|
22
41
|
/** @type Quality[] */
|
|
23
42
|
static BITRATES = [
|
|
@@ -29,15 +48,14 @@ class MediaBunnyRecorder extends CanvasRecorder {
|
|
|
29
48
|
];
|
|
30
49
|
|
|
31
50
|
/**
|
|
32
|
-
*
|
|
33
|
-
* @param {HTMLCanvasElement} canvas
|
|
34
|
-
* @param {
|
|
35
|
-
* @param {codec} options.string
|
|
51
|
+
* Create a MediaBunny recorder
|
|
52
|
+
* @param {HTMLCanvasElement} canvas - The canvas to record
|
|
53
|
+
* @param {MediaBunnyRecorderOptions} options - Recording options
|
|
36
54
|
*/
|
|
37
55
|
constructor(canvas, { codec, ...options }) {
|
|
38
56
|
super(canvas, options);
|
|
39
57
|
|
|
40
|
-
/** @type {
|
|
58
|
+
/** @type {VideoCodec} */
|
|
41
59
|
this.codec = codec;
|
|
42
60
|
|
|
43
61
|
const outputFormats = new Map();
|
|
@@ -72,23 +90,39 @@ class MediaBunnyRecorder extends CanvasRecorder {
|
|
|
72
90
|
});
|
|
73
91
|
}
|
|
74
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Load and start the output
|
|
95
|
+
* @returns {Promise<void>}
|
|
96
|
+
*/
|
|
75
97
|
async load() {
|
|
76
98
|
await this.output.start();
|
|
77
99
|
}
|
|
78
100
|
|
|
79
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Process a single frame
|
|
103
|
+
* @param {import('./CanvasRecorder').TickData} tickData - Frame data
|
|
104
|
+
* @returns {Promise<void>}
|
|
105
|
+
*/
|
|
106
|
+
async tick({ frameCount }) {
|
|
80
107
|
const timestamp = frameCount / this.framerate;
|
|
81
108
|
|
|
82
109
|
this.videoSource.add(timestamp, this.frameDuration / 1000);
|
|
83
110
|
}
|
|
84
111
|
|
|
112
|
+
/**
|
|
113
|
+
* End recording and create video blob
|
|
114
|
+
* @returns {Promise<void>}
|
|
115
|
+
*/
|
|
85
116
|
async end() {
|
|
86
117
|
await this.output.finalize();
|
|
87
118
|
|
|
88
119
|
const { mimeType } = this.output.format;
|
|
89
|
-
const
|
|
120
|
+
const target = /** @type {BufferTarget} */ (this.output.target);
|
|
121
|
+
const buffer = target.buffer;
|
|
90
122
|
|
|
91
|
-
|
|
123
|
+
if (buffer) {
|
|
124
|
+
this.result = new Blob([buffer], { type: mimeType });
|
|
125
|
+
}
|
|
92
126
|
|
|
93
127
|
super.end();
|
|
94
128
|
}
|
|
@@ -3,15 +3,24 @@ import { changeDpiDataUrl } from 'changedpi';
|
|
|
3
3
|
const supportedEncodings = ['image/png', 'image/jpeg', 'image/webp'];
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
* @
|
|
8
|
-
* @
|
|
9
|
-
* @
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
* @
|
|
14
|
-
* @
|
|
6
|
+
* @typedef {Object} ExportCanvasOptions
|
|
7
|
+
* @property {string} [encoding='image/png'] - Image MIME type (image/png, image/jpeg, image/webp)
|
|
8
|
+
* @property {number} [encodingQuality=0.92] - Image quality (0-1)
|
|
9
|
+
* @property {number} [pixelsPerInch=72] - DPI/PPI for the exported image
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {Object} ExportCanvasResult
|
|
14
|
+
* @property {string} extension - File extension (e.g., '.png', '.jpg')
|
|
15
|
+
* @property {string} dataURL - Data URL of the exported canvas
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Export a canvas to a data URL with specified encoding and DPI
|
|
20
|
+
* @param {HTMLCanvasElement} canvas - The canvas to export
|
|
21
|
+
* @param {ExportCanvasOptions} [options={}] - Export options
|
|
22
|
+
* @returns {ExportCanvasResult}
|
|
23
|
+
* @throws {Error} If encoding is not supported
|
|
15
24
|
*/
|
|
16
25
|
export function exportCanvas(
|
|
17
26
|
canvas,
|
|
@@ -5,32 +5,36 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* @
|
|
9
|
-
* @
|
|
10
|
-
* @
|
|
11
|
-
* @
|
|
12
|
-
* @
|
|
13
|
-
* @
|
|
14
|
-
* @
|
|
8
|
+
* @typedef {object} PreviewParams
|
|
9
|
+
* @property {number} id
|
|
10
|
+
* @property {HTMLCanvasElement} canvas
|
|
11
|
+
* @property {HTMLElement} container
|
|
12
|
+
* @property {number} width
|
|
13
|
+
* @property {number} height
|
|
14
|
+
* @property {number} pixelRatio
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {PreviewParams} params
|
|
15
19
|
* @returns {MountParams2DRenderer}
|
|
16
20
|
*/
|
|
17
21
|
export let onMountPreview = ({ canvas }) => {
|
|
22
|
+
const context = canvas.getContext('2d');
|
|
23
|
+
|
|
24
|
+
if (!context) {
|
|
25
|
+
throw new Error(`Cannot get CanvasRenderingContext2D from canvas`);
|
|
26
|
+
}
|
|
27
|
+
|
|
18
28
|
return {
|
|
19
29
|
canvas,
|
|
20
|
-
context
|
|
30
|
+
context,
|
|
21
31
|
};
|
|
22
32
|
};
|
|
23
33
|
|
|
24
34
|
/**
|
|
25
|
-
* @param {MountParams2DRenderer} params
|
|
26
|
-
* @param {number} params.id
|
|
27
|
-
* @param {HTMLCanvasElement} params.canvas
|
|
28
|
-
* @param {HTMLElement} params.container
|
|
29
|
-
* @param {number} params.width
|
|
30
|
-
* @param {number} params.height
|
|
31
|
-
* @param {number} params.pixelRatio
|
|
35
|
+
* @param {MountParams2DRenderer & PreviewParams} params
|
|
32
36
|
*/
|
|
33
|
-
export let onResizePreview = ({
|
|
37
|
+
export let onResizePreview = ({ canvas, width, height, pixelRatio }) => {
|
|
34
38
|
canvas.width = width * pixelRatio;
|
|
35
39
|
canvas.height = height * pixelRatio;
|
|
36
40
|
canvas.style.width = `${width}px`;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import p5 from 'p5';
|
|
2
|
-
import { client } from '
|
|
2
|
+
import { client } from '../client';
|
|
3
3
|
import { getShaderPath } from '../utils/glsl.utils';
|
|
4
4
|
import { clearError } from '../state/errors.svelte';
|
|
5
5
|
|
|
@@ -29,8 +29,16 @@ let previews = [];
|
|
|
29
29
|
* @param {number} params.pixelRatio
|
|
30
30
|
* @returns {MountParamsP5GLRenderer}
|
|
31
31
|
*/
|
|
32
|
-
export let onMountPreview = ({
|
|
32
|
+
export let onMountPreview = ({
|
|
33
|
+
id,
|
|
34
|
+
container,
|
|
35
|
+
canvas,
|
|
36
|
+
width,
|
|
37
|
+
height,
|
|
38
|
+
pixelRatio,
|
|
39
|
+
}) => {
|
|
33
40
|
const p = new p5((sketch) => {
|
|
41
|
+
sketch.pixelDensity(pixelRatio);
|
|
34
42
|
sketch.setup = () => {
|
|
35
43
|
sketch.createCanvas(width, height, 'webgl', canvas);
|
|
36
44
|
};
|
|
@@ -49,7 +57,7 @@ export let onMountPreview = ({ id, container, canvas, width, height }) => {
|
|
|
49
57
|
};
|
|
50
58
|
|
|
51
59
|
/**
|
|
52
|
-
* @param {
|
|
60
|
+
* @param {object} params
|
|
53
61
|
* @param {number} params.id
|
|
54
62
|
* @param {HTMLCanvasElement} params.canvas
|
|
55
63
|
* @param {HTMLDivElement} params.container
|
|
@@ -65,7 +73,7 @@ export let onBeforeUpdatePreview = ({ id }) => {
|
|
|
65
73
|
};
|
|
66
74
|
|
|
67
75
|
/**
|
|
68
|
-
* @param {
|
|
76
|
+
* @param {object} params
|
|
69
77
|
* @param {number} params.id
|
|
70
78
|
* @param {HTMLCanvasElement} params.canvas
|
|
71
79
|
* @param {HTMLDivElement} params.container
|
|
@@ -86,7 +94,7 @@ export let onAfterUpdatePreview = ({ id }) => {
|
|
|
86
94
|
};
|
|
87
95
|
|
|
88
96
|
/**
|
|
89
|
-
* @param {
|
|
97
|
+
* @param {object} params
|
|
90
98
|
* @param {number} params.id
|
|
91
99
|
* @param {HTMLCanvasElement} params.canvas
|
|
92
100
|
* @param {number} params.width
|
|
@@ -103,7 +111,7 @@ export let onResizePreview = ({ id, width, height, pixelRatio }) => {
|
|
|
103
111
|
};
|
|
104
112
|
|
|
105
113
|
/**
|
|
106
|
-
* @param {
|
|
114
|
+
* @param {object} params
|
|
107
115
|
* @param {number} params.id
|
|
108
116
|
* @param {HTMLCanvasElement} params.canvas
|
|
109
117
|
* @param {HTMLElement} params.container
|
|
@@ -25,8 +25,16 @@ let previews = [];
|
|
|
25
25
|
* @param {number} params.pixelRatio
|
|
26
26
|
* @returns {MountParamsP5Renderer}
|
|
27
27
|
*/
|
|
28
|
-
export let onMountPreview = ({
|
|
28
|
+
export let onMountPreview = ({
|
|
29
|
+
id,
|
|
30
|
+
container,
|
|
31
|
+
canvas,
|
|
32
|
+
width,
|
|
33
|
+
height,
|
|
34
|
+
pixelRatio,
|
|
35
|
+
}) => {
|
|
29
36
|
const p = new p5((sketch) => {
|
|
37
|
+
sketch.pixelDensity(pixelRatio);
|
|
30
38
|
sketch.setup = () => {
|
|
31
39
|
sketch.createCanvas(width, height, canvas);
|
|
32
40
|
};
|