asciify-engine 1.0.31 → 1.0.34
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/README.md +1 -1
- package/dist/index.cjs +124 -124
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +87 -45
- package/dist/index.d.ts +87 -45
- package/dist/index.js +122 -123
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -711,66 +711,108 @@ declare function renderTextBackground(ctx: CanvasRenderingContext2D, width: numb
|
|
|
711
711
|
intensity?: number;
|
|
712
712
|
} | null): void;
|
|
713
713
|
|
|
714
|
-
|
|
715
|
-
* record() — capture a rolling frame buffer from a running ASCII canvas
|
|
716
|
-
* and export it as a downloadable animated GIF or WebP data URL.
|
|
717
|
-
*
|
|
718
|
-
* Usage:
|
|
719
|
-
* ```ts
|
|
720
|
-
* const recorder = createRecorder(canvasEl, { fps: 15, maxFrames: 120 });
|
|
721
|
-
* recorder.start();
|
|
722
|
-
* // ... after a few seconds ...
|
|
723
|
-
* const dataUrl = await recorder.stop(); // 'data:image/gif;base64,...'
|
|
724
|
-
* ```
|
|
725
|
-
*/
|
|
726
|
-
interface RecorderOptions {
|
|
727
|
-
/** Target capture frame rate (default: 15) */
|
|
728
|
-
fps?: number;
|
|
729
|
-
/** Maximum number of frames to buffer (default: 120 → 8 s at 15 fps) */
|
|
730
|
-
maxFrames?: number;
|
|
714
|
+
interface SnapshotOptions {
|
|
731
715
|
/**
|
|
732
|
-
*
|
|
733
|
-
* - '
|
|
734
|
-
* - '
|
|
735
|
-
* - '
|
|
736
|
-
* Default: 'gif'
|
|
716
|
+
* Image format.
|
|
717
|
+
* - 'png' — lossless (default)
|
|
718
|
+
* - 'jpeg' — smaller, no transparency
|
|
719
|
+
* - 'webp' — best compression with transparency
|
|
737
720
|
*/
|
|
738
|
-
format?: '
|
|
739
|
-
/**
|
|
721
|
+
format?: 'png' | 'jpeg' | 'webp';
|
|
722
|
+
/** 0–1 quality for jpeg/webp. Default: 0.92 */
|
|
740
723
|
quality?: number;
|
|
741
724
|
/**
|
|
742
|
-
* Scale factor applied
|
|
743
|
-
* Use
|
|
725
|
+
* Scale factor applied before capture. Default: 1.
|
|
726
|
+
* Use 2 for a high-resolution export at 2× the canvas size.
|
|
744
727
|
*/
|
|
745
728
|
scale?: number;
|
|
746
729
|
}
|
|
747
|
-
interface Recorder {
|
|
748
|
-
/** Start capturing frames from the canvas. */
|
|
749
|
-
start(): void;
|
|
750
|
-
/**
|
|
751
|
-
* Stop capturing and encode.
|
|
752
|
-
* Resolves with a data URL (gif/webp) or JSON string (png-sequence).
|
|
753
|
-
*/
|
|
754
|
-
stop(): Promise<string>;
|
|
755
|
-
/** True while recording. */
|
|
756
|
-
readonly isRecording: boolean;
|
|
757
|
-
/** Number of frames captured so far. */
|
|
758
|
-
readonly frameCount: number;
|
|
759
|
-
}
|
|
760
730
|
/**
|
|
761
|
-
*
|
|
731
|
+
* Capture a single frame from a canvas as a Blob.
|
|
732
|
+
*
|
|
733
|
+
* @example
|
|
734
|
+
* ```ts
|
|
735
|
+
* const blob = await captureSnapshot(canvas);
|
|
736
|
+
* const url = URL.createObjectURL(blob);
|
|
737
|
+
* ```
|
|
762
738
|
*/
|
|
763
|
-
declare function
|
|
739
|
+
declare function captureSnapshot(canvas: HTMLCanvasElement, { format, quality, scale }?: SnapshotOptions): Promise<Blob>;
|
|
764
740
|
/**
|
|
765
|
-
*
|
|
741
|
+
* Capture a single frame and immediately trigger a browser download.
|
|
766
742
|
*
|
|
767
743
|
* @example
|
|
768
744
|
* ```ts
|
|
769
|
-
* await
|
|
745
|
+
* await snapshotAndDownload(canvas);
|
|
746
|
+
* await snapshotAndDownload(canvas, { format: 'jpeg', filename: 'my-art' });
|
|
770
747
|
* ```
|
|
771
748
|
*/
|
|
772
|
-
declare function
|
|
749
|
+
declare function snapshotAndDownload(canvas: HTMLCanvasElement, options?: SnapshotOptions & {
|
|
773
750
|
filename?: string;
|
|
774
751
|
}): Promise<void>;
|
|
775
752
|
|
|
776
|
-
|
|
753
|
+
/**
|
|
754
|
+
* asciifyWebcam — live webcam → ASCII art on canvas.
|
|
755
|
+
*
|
|
756
|
+
* Requests camera access, attaches the stream to a hidden video element, and
|
|
757
|
+
* runs a rAF loop that converts each frame to ASCII and renders it onto a
|
|
758
|
+
* supplied canvas.
|
|
759
|
+
*
|
|
760
|
+
* @example
|
|
761
|
+
* const stop = await asciifyWebcam(canvas);
|
|
762
|
+
* // later: stop();
|
|
763
|
+
*
|
|
764
|
+
* @example
|
|
765
|
+
* const stop = await asciifyWebcam(canvas, {
|
|
766
|
+
* fontSize: 8,
|
|
767
|
+
* style: 'terminal',
|
|
768
|
+
* mirror: true, // horizontal flip (selfie mode)
|
|
769
|
+
* constraints: { facingMode: 'user' },
|
|
770
|
+
* });
|
|
771
|
+
*/
|
|
772
|
+
|
|
773
|
+
interface WebcamOptions {
|
|
774
|
+
/** Character size in pixels. Default: 10 */
|
|
775
|
+
fontSize?: number;
|
|
776
|
+
/** Art style preset. Default: 'classic' */
|
|
777
|
+
style?: ArtStyle;
|
|
778
|
+
/** Extra AsciiOptions merged on top of the preset */
|
|
779
|
+
options?: Partial<AsciiOptions>;
|
|
780
|
+
/**
|
|
781
|
+
* Called every frame to get the latest options. Takes priority over `options`.
|
|
782
|
+
* Use this to keep the rendering in sync with live UI controls without
|
|
783
|
+
* restarting the camera.
|
|
784
|
+
*
|
|
785
|
+
* @example
|
|
786
|
+
* const optionsRef = useRef(currentOptions);
|
|
787
|
+
* optionsRef.current = currentOptions;
|
|
788
|
+
* asciifyWebcam(canvas, { liveOptions: () => optionsRef.current });
|
|
789
|
+
*/
|
|
790
|
+
liveOptions?: () => Partial<AsciiOptions>;
|
|
791
|
+
/**
|
|
792
|
+
* Flip the output horizontally so it reads like a mirror / selfie camera.
|
|
793
|
+
* Default: true
|
|
794
|
+
*/
|
|
795
|
+
mirror?: boolean;
|
|
796
|
+
/**
|
|
797
|
+
* Passed directly to `getUserMedia({ video: constraints })`.
|
|
798
|
+
* Defaults to `{ facingMode: 'user' }`.
|
|
799
|
+
*/
|
|
800
|
+
constraints?: MediaTrackConstraints;
|
|
801
|
+
/**
|
|
802
|
+
* Device pixel ratio used to compute logical render dimensions from the
|
|
803
|
+
* canvas's physical pixel size. Defaults to `window.devicePixelRatio ?? 1`.
|
|
804
|
+
* Set this when you size the canvas at physical resolution (e.g. width × dpr)
|
|
805
|
+
* so that ASCII column/row counts are based on CSS pixels, not physical ones.
|
|
806
|
+
*/
|
|
807
|
+
dpr?: number;
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Start a live webcam ASCII-art loop and render it onto `canvas`.
|
|
811
|
+
* Returns a `stop()` function that cancels the loop and releases the camera.
|
|
812
|
+
*
|
|
813
|
+
* Throws if the browser doesn't support `getUserMedia` or the user denies
|
|
814
|
+
* camera permission.
|
|
815
|
+
*/
|
|
816
|
+
declare function asciifyWebcam(canvas: HTMLCanvasElement, { fontSize, style, options, liveOptions, mirror, constraints, dpr: dprOverride, }?: WebcamOptions): Promise<() => void>;
|
|
817
|
+
|
|
818
|
+
export { ART_STYLE_PRESETS, type AnimationStyle, type ArtStyle, type AsciiBackgroundOptions, type AsciiCell, type AsciiFrame, type AsciiOptions, type AsciiResult, type AsciifySimpleOptions, type AuroraBackgroundOptions, CHARSETS, type CharsetKey, type CircuitBackgroundOptions, type ColorMode, DEFAULT_OPTIONS, type DnaBackgroundOptions, type FireBackgroundOptions, type GridBackgroundOptions, HOVER_PRESETS, type HoverEffect, type HoverPreset, type MorphBackgroundOptions, type MountWaveOptions, type NoiseBackgroundOptions, PALETTE_THEMES, type PaletteTheme, type PulseBackgroundOptions, type RainBackgroundOptions, type RenderMode, type SilkBackgroundOptions, type SnapshotOptions, type SourceType, type StarsBackgroundOptions, type TerrainBackgroundOptions, type TextBackgroundOptions, type VoidBackgroundOptions, type WaveBackgroundOptions, type WebcamOptions, asciiBackground, asciiText, asciiTextAnsi, asciify, asciifyGif, asciifyVideo, asciifyWebcam, buildTextFrame, captureSnapshot, gifToAsciiFrames, imageToAsciiFrame, mountWaveBackground, renderAuroraBackground, renderCircuitBackground, renderDnaBackground, renderFireBackground, renderFrameToCanvas, renderGridBackground, renderMorphBackground, renderNoiseBackground, renderPulseBackground, renderRainBackground, renderSilkBackground, renderStarsBackground, renderTerrainBackground, renderTextBackground, renderVoidBackground, renderWaveBackground, snapshotAndDownload, videoToAsciiFrames };
|
package/dist/index.d.ts
CHANGED
|
@@ -711,66 +711,108 @@ declare function renderTextBackground(ctx: CanvasRenderingContext2D, width: numb
|
|
|
711
711
|
intensity?: number;
|
|
712
712
|
} | null): void;
|
|
713
713
|
|
|
714
|
-
|
|
715
|
-
* record() — capture a rolling frame buffer from a running ASCII canvas
|
|
716
|
-
* and export it as a downloadable animated GIF or WebP data URL.
|
|
717
|
-
*
|
|
718
|
-
* Usage:
|
|
719
|
-
* ```ts
|
|
720
|
-
* const recorder = createRecorder(canvasEl, { fps: 15, maxFrames: 120 });
|
|
721
|
-
* recorder.start();
|
|
722
|
-
* // ... after a few seconds ...
|
|
723
|
-
* const dataUrl = await recorder.stop(); // 'data:image/gif;base64,...'
|
|
724
|
-
* ```
|
|
725
|
-
*/
|
|
726
|
-
interface RecorderOptions {
|
|
727
|
-
/** Target capture frame rate (default: 15) */
|
|
728
|
-
fps?: number;
|
|
729
|
-
/** Maximum number of frames to buffer (default: 120 → 8 s at 15 fps) */
|
|
730
|
-
maxFrames?: number;
|
|
714
|
+
interface SnapshotOptions {
|
|
731
715
|
/**
|
|
732
|
-
*
|
|
733
|
-
* - '
|
|
734
|
-
* - '
|
|
735
|
-
* - '
|
|
736
|
-
* Default: 'gif'
|
|
716
|
+
* Image format.
|
|
717
|
+
* - 'png' — lossless (default)
|
|
718
|
+
* - 'jpeg' — smaller, no transparency
|
|
719
|
+
* - 'webp' — best compression with transparency
|
|
737
720
|
*/
|
|
738
|
-
format?: '
|
|
739
|
-
/**
|
|
721
|
+
format?: 'png' | 'jpeg' | 'webp';
|
|
722
|
+
/** 0–1 quality for jpeg/webp. Default: 0.92 */
|
|
740
723
|
quality?: number;
|
|
741
724
|
/**
|
|
742
|
-
* Scale factor applied
|
|
743
|
-
* Use
|
|
725
|
+
* Scale factor applied before capture. Default: 1.
|
|
726
|
+
* Use 2 for a high-resolution export at 2× the canvas size.
|
|
744
727
|
*/
|
|
745
728
|
scale?: number;
|
|
746
729
|
}
|
|
747
|
-
interface Recorder {
|
|
748
|
-
/** Start capturing frames from the canvas. */
|
|
749
|
-
start(): void;
|
|
750
|
-
/**
|
|
751
|
-
* Stop capturing and encode.
|
|
752
|
-
* Resolves with a data URL (gif/webp) or JSON string (png-sequence).
|
|
753
|
-
*/
|
|
754
|
-
stop(): Promise<string>;
|
|
755
|
-
/** True while recording. */
|
|
756
|
-
readonly isRecording: boolean;
|
|
757
|
-
/** Number of frames captured so far. */
|
|
758
|
-
readonly frameCount: number;
|
|
759
|
-
}
|
|
760
730
|
/**
|
|
761
|
-
*
|
|
731
|
+
* Capture a single frame from a canvas as a Blob.
|
|
732
|
+
*
|
|
733
|
+
* @example
|
|
734
|
+
* ```ts
|
|
735
|
+
* const blob = await captureSnapshot(canvas);
|
|
736
|
+
* const url = URL.createObjectURL(blob);
|
|
737
|
+
* ```
|
|
762
738
|
*/
|
|
763
|
-
declare function
|
|
739
|
+
declare function captureSnapshot(canvas: HTMLCanvasElement, { format, quality, scale }?: SnapshotOptions): Promise<Blob>;
|
|
764
740
|
/**
|
|
765
|
-
*
|
|
741
|
+
* Capture a single frame and immediately trigger a browser download.
|
|
766
742
|
*
|
|
767
743
|
* @example
|
|
768
744
|
* ```ts
|
|
769
|
-
* await
|
|
745
|
+
* await snapshotAndDownload(canvas);
|
|
746
|
+
* await snapshotAndDownload(canvas, { format: 'jpeg', filename: 'my-art' });
|
|
770
747
|
* ```
|
|
771
748
|
*/
|
|
772
|
-
declare function
|
|
749
|
+
declare function snapshotAndDownload(canvas: HTMLCanvasElement, options?: SnapshotOptions & {
|
|
773
750
|
filename?: string;
|
|
774
751
|
}): Promise<void>;
|
|
775
752
|
|
|
776
|
-
|
|
753
|
+
/**
|
|
754
|
+
* asciifyWebcam — live webcam → ASCII art on canvas.
|
|
755
|
+
*
|
|
756
|
+
* Requests camera access, attaches the stream to a hidden video element, and
|
|
757
|
+
* runs a rAF loop that converts each frame to ASCII and renders it onto a
|
|
758
|
+
* supplied canvas.
|
|
759
|
+
*
|
|
760
|
+
* @example
|
|
761
|
+
* const stop = await asciifyWebcam(canvas);
|
|
762
|
+
* // later: stop();
|
|
763
|
+
*
|
|
764
|
+
* @example
|
|
765
|
+
* const stop = await asciifyWebcam(canvas, {
|
|
766
|
+
* fontSize: 8,
|
|
767
|
+
* style: 'terminal',
|
|
768
|
+
* mirror: true, // horizontal flip (selfie mode)
|
|
769
|
+
* constraints: { facingMode: 'user' },
|
|
770
|
+
* });
|
|
771
|
+
*/
|
|
772
|
+
|
|
773
|
+
interface WebcamOptions {
|
|
774
|
+
/** Character size in pixels. Default: 10 */
|
|
775
|
+
fontSize?: number;
|
|
776
|
+
/** Art style preset. Default: 'classic' */
|
|
777
|
+
style?: ArtStyle;
|
|
778
|
+
/** Extra AsciiOptions merged on top of the preset */
|
|
779
|
+
options?: Partial<AsciiOptions>;
|
|
780
|
+
/**
|
|
781
|
+
* Called every frame to get the latest options. Takes priority over `options`.
|
|
782
|
+
* Use this to keep the rendering in sync with live UI controls without
|
|
783
|
+
* restarting the camera.
|
|
784
|
+
*
|
|
785
|
+
* @example
|
|
786
|
+
* const optionsRef = useRef(currentOptions);
|
|
787
|
+
* optionsRef.current = currentOptions;
|
|
788
|
+
* asciifyWebcam(canvas, { liveOptions: () => optionsRef.current });
|
|
789
|
+
*/
|
|
790
|
+
liveOptions?: () => Partial<AsciiOptions>;
|
|
791
|
+
/**
|
|
792
|
+
* Flip the output horizontally so it reads like a mirror / selfie camera.
|
|
793
|
+
* Default: true
|
|
794
|
+
*/
|
|
795
|
+
mirror?: boolean;
|
|
796
|
+
/**
|
|
797
|
+
* Passed directly to `getUserMedia({ video: constraints })`.
|
|
798
|
+
* Defaults to `{ facingMode: 'user' }`.
|
|
799
|
+
*/
|
|
800
|
+
constraints?: MediaTrackConstraints;
|
|
801
|
+
/**
|
|
802
|
+
* Device pixel ratio used to compute logical render dimensions from the
|
|
803
|
+
* canvas's physical pixel size. Defaults to `window.devicePixelRatio ?? 1`.
|
|
804
|
+
* Set this when you size the canvas at physical resolution (e.g. width × dpr)
|
|
805
|
+
* so that ASCII column/row counts are based on CSS pixels, not physical ones.
|
|
806
|
+
*/
|
|
807
|
+
dpr?: number;
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Start a live webcam ASCII-art loop and render it onto `canvas`.
|
|
811
|
+
* Returns a `stop()` function that cancels the loop and releases the camera.
|
|
812
|
+
*
|
|
813
|
+
* Throws if the browser doesn't support `getUserMedia` or the user denies
|
|
814
|
+
* camera permission.
|
|
815
|
+
*/
|
|
816
|
+
declare function asciifyWebcam(canvas: HTMLCanvasElement, { fontSize, style, options, liveOptions, mirror, constraints, dpr: dprOverride, }?: WebcamOptions): Promise<() => void>;
|
|
817
|
+
|
|
818
|
+
export { ART_STYLE_PRESETS, type AnimationStyle, type ArtStyle, type AsciiBackgroundOptions, type AsciiCell, type AsciiFrame, type AsciiOptions, type AsciiResult, type AsciifySimpleOptions, type AuroraBackgroundOptions, CHARSETS, type CharsetKey, type CircuitBackgroundOptions, type ColorMode, DEFAULT_OPTIONS, type DnaBackgroundOptions, type FireBackgroundOptions, type GridBackgroundOptions, HOVER_PRESETS, type HoverEffect, type HoverPreset, type MorphBackgroundOptions, type MountWaveOptions, type NoiseBackgroundOptions, PALETTE_THEMES, type PaletteTheme, type PulseBackgroundOptions, type RainBackgroundOptions, type RenderMode, type SilkBackgroundOptions, type SnapshotOptions, type SourceType, type StarsBackgroundOptions, type TerrainBackgroundOptions, type TextBackgroundOptions, type VoidBackgroundOptions, type WaveBackgroundOptions, type WebcamOptions, asciiBackground, asciiText, asciiTextAnsi, asciify, asciifyGif, asciifyVideo, asciifyWebcam, buildTextFrame, captureSnapshot, gifToAsciiFrames, imageToAsciiFrame, mountWaveBackground, renderAuroraBackground, renderCircuitBackground, renderDnaBackground, renderFireBackground, renderFrameToCanvas, renderGridBackground, renderMorphBackground, renderNoiseBackground, renderPulseBackground, renderRainBackground, renderSilkBackground, renderStarsBackground, renderTerrainBackground, renderTextBackground, renderVoidBackground, renderWaveBackground, snapshotAndDownload, videoToAsciiFrames };
|
package/dist/index.js
CHANGED
|
@@ -2283,146 +2283,145 @@ function renderTextBackground(ctx, width, height, text, options = {}, hoverPos)
|
|
|
2283
2283
|
}
|
|
2284
2284
|
|
|
2285
2285
|
// src/core/record.ts
|
|
2286
|
-
function
|
|
2287
|
-
|
|
2288
|
-
fps = 15,
|
|
2289
|
-
maxFrames = 120,
|
|
2290
|
-
format = "gif",
|
|
2291
|
-
quality = 10,
|
|
2292
|
-
scale = 1
|
|
2293
|
-
} = options;
|
|
2294
|
-
const interval = 1e3 / fps;
|
|
2295
|
-
let recording = false;
|
|
2296
|
-
let timerId = -1;
|
|
2297
|
-
const blobs = [];
|
|
2298
|
-
const captureFrame = () => {
|
|
2299
|
-
if (!recording || blobs.length >= maxFrames) return;
|
|
2286
|
+
function captureSnapshot(canvas, { format = "png", quality = 0.92, scale = 1 } = {}) {
|
|
2287
|
+
return new Promise((resolve, reject) => {
|
|
2300
2288
|
let src = canvas;
|
|
2301
2289
|
if (scale !== 1) {
|
|
2302
2290
|
const off = document.createElement("canvas");
|
|
2303
2291
|
off.width = Math.round(canvas.width * scale);
|
|
2304
2292
|
off.height = Math.round(canvas.height * scale);
|
|
2305
2293
|
const offCtx = off.getContext("2d");
|
|
2294
|
+
if (!offCtx) {
|
|
2295
|
+
reject(new Error("captureSnapshot: could not get 2d context"));
|
|
2296
|
+
return;
|
|
2297
|
+
}
|
|
2306
2298
|
offCtx.drawImage(canvas, 0, 0, off.width, off.height);
|
|
2307
2299
|
src = off;
|
|
2308
2300
|
}
|
|
2309
|
-
|
|
2301
|
+
src.toBlob(
|
|
2302
|
+
(blob) => blob ? resolve(blob) : reject(new Error("captureSnapshot: toBlob returned null")),
|
|
2303
|
+
`image/${format}`,
|
|
2304
|
+
quality
|
|
2305
|
+
);
|
|
2306
|
+
});
|
|
2307
|
+
}
|
|
2308
|
+
async function snapshotAndDownload(canvas, options = {}) {
|
|
2309
|
+
const { filename = "asciify-snapshot", format = "png", ...snapOpts } = options;
|
|
2310
|
+
const blob = await captureSnapshot(canvas, { format, ...snapOpts });
|
|
2311
|
+
const ext = format === "jpeg" ? "jpg" : format;
|
|
2312
|
+
const a = document.createElement("a");
|
|
2313
|
+
a.href = URL.createObjectURL(blob);
|
|
2314
|
+
a.download = `${filename}.${ext}`;
|
|
2315
|
+
a.click();
|
|
2316
|
+
setTimeout(() => URL.revokeObjectURL(a.href), 1e4);
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
// src/core/webcam.ts
|
|
2320
|
+
async function asciifyWebcam(canvas, {
|
|
2321
|
+
fontSize = 10,
|
|
2322
|
+
style = "classic",
|
|
2323
|
+
options = {},
|
|
2324
|
+
liveOptions,
|
|
2325
|
+
mirror = true,
|
|
2326
|
+
constraints = { facingMode: "user" },
|
|
2327
|
+
dpr: dprOverride
|
|
2328
|
+
} = {}) {
|
|
2329
|
+
if (!navigator.mediaDevices?.getUserMedia) {
|
|
2330
|
+
throw new Error("asciifyWebcam: getUserMedia is not supported in this browser.");
|
|
2331
|
+
}
|
|
2332
|
+
const stream = await navigator.mediaDevices.getUserMedia({ video: constraints });
|
|
2333
|
+
const video = document.createElement("video");
|
|
2334
|
+
video.srcObject = stream;
|
|
2335
|
+
video.muted = true;
|
|
2336
|
+
video.playsInline = true;
|
|
2337
|
+
await new Promise((resolve, reject) => {
|
|
2338
|
+
video.onloadedmetadata = () => resolve();
|
|
2339
|
+
video.onerror = () => reject(new Error("asciifyWebcam: video stream failed to load."));
|
|
2340
|
+
video.play().catch(reject);
|
|
2341
|
+
});
|
|
2342
|
+
const merged = {
|
|
2343
|
+
...DEFAULT_OPTIONS,
|
|
2344
|
+
...ART_STYLE_PRESETS[style],
|
|
2345
|
+
...options,
|
|
2346
|
+
fontSize
|
|
2310
2347
|
};
|
|
2311
|
-
const
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
const img = new Image();
|
|
2326
|
-
img.onload = () => {
|
|
2327
|
-
gif.addFrame(img, { delay: interval, copy: true });
|
|
2328
|
-
loaded++;
|
|
2329
|
-
if (loaded === total) gif.render();
|
|
2330
|
-
};
|
|
2331
|
-
img.src = dataUrl;
|
|
2332
|
-
});
|
|
2333
|
-
gif.on("finished", (blob) => {
|
|
2334
|
-
const reader = new FileReader();
|
|
2335
|
-
reader.onload = () => resolve(reader.result);
|
|
2336
|
-
reader.readAsDataURL(blob);
|
|
2337
|
-
});
|
|
2338
|
-
gif.on("error", reject);
|
|
2339
|
-
});
|
|
2348
|
+
const ctx = canvas.getContext("2d");
|
|
2349
|
+
if (!ctx) throw new Error("asciifyWebcam: could not get 2d context from canvas.");
|
|
2350
|
+
const deviceRatio = dprOverride ?? (typeof window !== "undefined" ? window.devicePixelRatio : 1) ?? 1;
|
|
2351
|
+
if (deviceRatio !== 1) {
|
|
2352
|
+
ctx.scale(deviceRatio, deviceRatio);
|
|
2353
|
+
}
|
|
2354
|
+
let hoverPos = null;
|
|
2355
|
+
const smoothHover = { x: 0.5, y: 0.5, intensity: 0 };
|
|
2356
|
+
const onMouseMove = (e) => {
|
|
2357
|
+
const rect = canvas.getBoundingClientRect();
|
|
2358
|
+
hoverPos = {
|
|
2359
|
+
x: (e.clientX - rect.left) / rect.width,
|
|
2360
|
+
y: (e.clientY - rect.top) / rect.height
|
|
2361
|
+
};
|
|
2340
2362
|
};
|
|
2341
|
-
const
|
|
2342
|
-
|
|
2343
|
-
throw new Error("[asciify recorder] MediaRecorder not available in this browser.");
|
|
2344
|
-
}
|
|
2345
|
-
const off = document.createElement("canvas");
|
|
2346
|
-
if (frames.length === 0) return "";
|
|
2347
|
-
const probe = new Image();
|
|
2348
|
-
await new Promise((res) => {
|
|
2349
|
-
probe.onload = () => res();
|
|
2350
|
-
probe.src = frames[0];
|
|
2351
|
-
});
|
|
2352
|
-
off.width = probe.naturalWidth;
|
|
2353
|
-
off.height = probe.naturalHeight;
|
|
2354
|
-
const offCtx = off.getContext("2d");
|
|
2355
|
-
const stream = off.captureStream(_fps);
|
|
2356
|
-
const recorder = new MediaRecorder(stream, { mimeType: "video/webm;codecs=vp9" });
|
|
2357
|
-
const chunks = [];
|
|
2358
|
-
recorder.ondataavailable = (e) => chunks.push(e.data);
|
|
2359
|
-
return new Promise((resolve, reject) => {
|
|
2360
|
-
recorder.onstop = () => {
|
|
2361
|
-
const blob = new Blob(chunks, { type: "video/webm" });
|
|
2362
|
-
const reader = new FileReader();
|
|
2363
|
-
reader.onload = () => resolve(reader.result);
|
|
2364
|
-
reader.readAsDataURL(blob);
|
|
2365
|
-
};
|
|
2366
|
-
recorder.onerror = reject;
|
|
2367
|
-
recorder.start();
|
|
2368
|
-
let idx = 0;
|
|
2369
|
-
const drawNext = () => {
|
|
2370
|
-
if (idx >= frames.length) {
|
|
2371
|
-
recorder.stop();
|
|
2372
|
-
return;
|
|
2373
|
-
}
|
|
2374
|
-
const img = new Image();
|
|
2375
|
-
img.onload = () => {
|
|
2376
|
-
offCtx.drawImage(img, 0, 0);
|
|
2377
|
-
idx++;
|
|
2378
|
-
setTimeout(drawNext, interval);
|
|
2379
|
-
};
|
|
2380
|
-
img.src = frames[idx];
|
|
2381
|
-
};
|
|
2382
|
-
drawNext();
|
|
2383
|
-
});
|
|
2363
|
+
const onMouseLeave = () => {
|
|
2364
|
+
hoverPos = null;
|
|
2384
2365
|
};
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2366
|
+
if (merged.hoverStrength > 0) {
|
|
2367
|
+
canvas.addEventListener("mousemove", onMouseMove);
|
|
2368
|
+
canvas.addEventListener("mouseleave", onMouseLeave);
|
|
2369
|
+
}
|
|
2370
|
+
let cancelled = false;
|
|
2371
|
+
let animId;
|
|
2372
|
+
const startTime = performance.now();
|
|
2373
|
+
const tick = (timestamp) => {
|
|
2374
|
+
if (cancelled) return;
|
|
2375
|
+
if (video.readyState >= video.HAVE_CURRENT_DATA) {
|
|
2376
|
+
const displayW = canvas.width / deviceRatio;
|
|
2377
|
+
const displayH = canvas.height / deviceRatio;
|
|
2378
|
+
const elapsed = (timestamp - startTime) / 1e3;
|
|
2379
|
+
const frameOptions = liveOptions ? { ...merged, ...liveOptions() } : merged;
|
|
2380
|
+
const wantsHover = frameOptions.hoverStrength > 0;
|
|
2381
|
+
if (wantsHover) {
|
|
2382
|
+
canvas.addEventListener("mousemove", onMouseMove);
|
|
2383
|
+
canvas.addEventListener("mouseleave", onMouseLeave);
|
|
2384
|
+
} else {
|
|
2385
|
+
canvas.removeEventListener("mousemove", onMouseMove);
|
|
2386
|
+
canvas.removeEventListener("mouseleave", onMouseLeave);
|
|
2387
|
+
}
|
|
2388
|
+
const { frame } = imageToAsciiFrame(video, frameOptions, displayW, displayH);
|
|
2389
|
+
if (hoverPos) {
|
|
2390
|
+
const dx = hoverPos.x - smoothHover.x;
|
|
2391
|
+
const dy = hoverPos.y - smoothHover.y;
|
|
2392
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
2393
|
+
const speed = Math.min(0.25, 0.06 + dist * 0.8);
|
|
2394
|
+
smoothHover.x += dx * speed;
|
|
2395
|
+
smoothHover.y += dy * speed;
|
|
2396
|
+
smoothHover.intensity += (1 - smoothHover.intensity) * 0.12;
|
|
2397
|
+
} else {
|
|
2398
|
+
smoothHover.intensity *= 0.965;
|
|
2399
|
+
if (smoothHover.intensity < 3e-3) smoothHover.intensity = 0;
|
|
2405
2400
|
}
|
|
2406
|
-
|
|
2407
|
-
|
|
2401
|
+
const hoverArg = smoothHover.intensity > 3e-3 ? { x: smoothHover.x, y: smoothHover.y, intensity: smoothHover.intensity } : null;
|
|
2402
|
+
if (mirror) {
|
|
2403
|
+
ctx.save();
|
|
2404
|
+
ctx.scale(-1, 1);
|
|
2405
|
+
ctx.translate(-displayW, 0);
|
|
2406
|
+
renderFrameToCanvas(ctx, frame, frameOptions, displayW, displayH, elapsed, hoverArg);
|
|
2407
|
+
ctx.restore();
|
|
2408
|
+
} else {
|
|
2409
|
+
renderFrameToCanvas(ctx, frame, frameOptions, displayW, displayH, elapsed, hoverArg);
|
|
2408
2410
|
}
|
|
2409
|
-
return encodeGif(frames);
|
|
2410
2411
|
}
|
|
2412
|
+
animId = requestAnimationFrame(tick);
|
|
2413
|
+
};
|
|
2414
|
+
animId = requestAnimationFrame(tick);
|
|
2415
|
+
return () => {
|
|
2416
|
+
cancelled = true;
|
|
2417
|
+
cancelAnimationFrame(animId);
|
|
2418
|
+
canvas.removeEventListener("mousemove", onMouseMove);
|
|
2419
|
+
canvas.removeEventListener("mouseleave", onMouseLeave);
|
|
2420
|
+
stream.getTracks().forEach((t) => t.stop());
|
|
2421
|
+
video.srcObject = null;
|
|
2411
2422
|
};
|
|
2412
|
-
}
|
|
2413
|
-
async function recordAndDownload(canvas, durationMs, options = {}) {
|
|
2414
|
-
const { filename = "asciify-recording", ...recOpts } = options;
|
|
2415
|
-
const recorder = createRecorder(canvas, recOpts);
|
|
2416
|
-
recorder.start();
|
|
2417
|
-
await new Promise((res) => setTimeout(res, durationMs));
|
|
2418
|
-
const dataUrl = await recorder.stop();
|
|
2419
|
-
const ext = options.format === "webp" ? "webm" : options.format === "png-sequence" ? "json" : "gif";
|
|
2420
|
-
const a = document.createElement("a");
|
|
2421
|
-
a.href = dataUrl;
|
|
2422
|
-
a.download = `${filename}.${ext}`;
|
|
2423
|
-
a.click();
|
|
2424
2423
|
}
|
|
2425
2424
|
|
|
2426
|
-
export { ART_STYLE_PRESETS, CHARSETS, DEFAULT_OPTIONS, HOVER_PRESETS, PALETTE_THEMES, asciiBackground, asciiText, asciiTextAnsi, asciify, asciifyGif, asciifyVideo, buildTextFrame,
|
|
2425
|
+
export { ART_STYLE_PRESETS, CHARSETS, DEFAULT_OPTIONS, HOVER_PRESETS, PALETTE_THEMES, asciiBackground, asciiText, asciiTextAnsi, asciify, asciifyGif, asciifyVideo, asciifyWebcam, buildTextFrame, captureSnapshot, gifToAsciiFrames, imageToAsciiFrame, mountWaveBackground, renderAuroraBackground, renderCircuitBackground, renderDnaBackground, renderFireBackground, renderFrameToCanvas, renderGridBackground, renderMorphBackground, renderNoiseBackground, renderPulseBackground, renderRainBackground, renderSilkBackground, renderStarsBackground, renderTerrainBackground, renderTextBackground, renderVoidBackground, renderWaveBackground, snapshotAndDownload, videoToAsciiFrames };
|
|
2427
2426
|
//# sourceMappingURL=index.js.map
|
|
2428
2427
|
//# sourceMappingURL=index.js.map
|