@zumer/snapdom-plugins 4.1.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +2 -2
- package/ascii-export.d.ts +10 -0
- package/ascii-export.js +70 -27
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Plugin changes
|
|
2
2
|
|
|
3
|
+
## 4.2.0
|
|
4
|
+
|
|
5
|
+
- Export asciiFromCanvas(source, options) from @zumer/snapdom-plugins/ascii-export: the ASCII conversion on its own, for a canvas, an image, a video frame or a stream frame. toAscii() keeps its output.
|
|
6
|
+
- Add asciiStream(element, options): stream() plus asciiFromCanvas() on every frame, with onText(text, frame).
|
|
7
|
+
|
|
8
|
+
Requires SnapDOM 3.4.0 or newer within 3.x.
|
|
9
|
+
|
|
3
10
|
## 4.1.0
|
|
4
11
|
|
|
5
12
|
- Add the stream plugin at @zumer/snapdom-plugins/stream: a live canvas that mirrors an element and repaints only the boxes that changed, for WebGL textures, video overlays and WebRTC tracks.
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @zumer/snapdom-plugins
|
|
2
2
|
|
|
3
|
-
Official plugins for [SnapDOM](https://github.com/zumerlab/snapdom), a high-performance engine
|
|
4
|
-
|
|
3
|
+
Official plugins for [SnapDOM](https://github.com/zumerlab/snapdom), a high-performance DOM engine
|
|
4
|
+
that exports a rendered element as PNG, SVG or canvas in the browser. Core exports images and
|
|
5
5
|
canvases; this package adds HTML, structured context, element maps, PDF, editable vectors, ASCII,
|
|
6
6
|
visual transforms, live GIF/video recording and live DOM streaming.
|
|
7
7
|
|
package/ascii-export.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* at capture time.
|
|
7
7
|
*/
|
|
8
8
|
import type { SnapdomPlugin } from '@zumer/snapdom';
|
|
9
|
+
import type { Stream, StreamOptions, StreamFrame } from './stream.js';
|
|
9
10
|
|
|
10
11
|
export interface AsciiExportOptions {
|
|
11
12
|
/** Character width of the output. */
|
|
@@ -23,8 +24,17 @@ export interface AsciiExportOptions {
|
|
|
23
24
|
/** Adds `result.toAscii()`. */
|
|
24
25
|
export declare function asciiExport(options?: AsciiExportOptions): SnapdomPlugin;
|
|
25
26
|
|
|
27
|
+
/** ASCII art from any drawable (a canvas, an image, a video frame, a `stream()` frame). No capture involved. */
|
|
28
|
+
export declare function asciiFromCanvas(source: CanvasImageSource & { width: number; height: number }, options?: AsciiExportOptions & { charset?: string | string[] }): string;
|
|
29
|
+
|
|
26
30
|
declare module '@zumer/snapdom' {
|
|
27
31
|
interface CaptureResult {
|
|
28
32
|
toAscii(options?: AsciiExportOptions): Promise<string>;
|
|
29
33
|
}
|
|
30
34
|
}
|
|
35
|
+
|
|
36
|
+
/** `stream()` plus `asciiFromCanvas()` on every frame; `onText` receives the text (or HTML when `format: 'html'`). */
|
|
37
|
+
export declare function asciiStream(
|
|
38
|
+
element: Element,
|
|
39
|
+
options?: AsciiExportOptions & { charset?: string | string[] } & StreamOptions & { onText?: (text: string, frame: StreamFrame) => void }
|
|
40
|
+
): Stream;
|
package/ascii-export.js
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* @param {number} [options.contrast=0] - Non-negative luminance contrast boost
|
|
13
13
|
* @returns {Object} SnapDOM plugin
|
|
14
14
|
*/
|
|
15
|
+
import { stream } from './stream.js';
|
|
16
|
+
|
|
15
17
|
export function asciiExport(options = {}) {
|
|
16
18
|
const {
|
|
17
19
|
width: charWidth = 80,
|
|
@@ -46,35 +48,76 @@ export function asciiExport(options = {}) {
|
|
|
46
48
|
...opts, width: ctx.width, height: ctx.height, scale: 1, dpr: 1, canvas: null,
|
|
47
49
|
backgroundColor: !opts.backgroundColor || opts.backgroundColor === 'transparent' ? '#ffffff' : opts.backgroundColor,
|
|
48
50
|
});
|
|
49
|
-
|
|
50
|
-
const canvas = document.createElement('canvas');
|
|
51
|
-
canvas.width = cols;
|
|
52
|
-
canvas.height = rows;
|
|
53
|
-
const c = canvas.getContext('2d');
|
|
54
|
-
// Squash the bitmap, not the SVG viewport: Firefox preserves its viewBox aspect ratio.
|
|
55
|
-
c.drawImage(source, 0, 0, cols, rows);
|
|
56
|
-
const data = c.getImageData(0, 0, cols, rows).data;
|
|
57
|
-
|
|
58
|
-
const colored = outputFormat === 'html';
|
|
59
|
-
let ascii = colored ? '<pre style="margin:0;font:7px/1 monospace;white-space:pre;overflow-wrap:normal;max-width:100%;overflow:auto;background:white">' : '';
|
|
60
|
-
for (let y = 0; y < rows; y++) {
|
|
61
|
-
for (let x = 0; x < cols; x++) {
|
|
62
|
-
const i = (y * cols + x) * 4;
|
|
63
|
-
let lum = 1 - (data[i] * 0.299 + data[i + 1] * 0.587 + data[i + 2] * 0.114) / 255;
|
|
64
|
-
lum = Math.max(0, Math.min(1, (1 + gain) * (lum - 0.5) + 0.5));
|
|
65
|
-
if (inv) lum = 1 - lum;
|
|
66
|
-
const ci = Math.min(Math.floor(lum * chars.length), chars.length - 1);
|
|
67
|
-
const char = chars[ci];
|
|
68
|
-
if (colored) {
|
|
69
|
-
const safe = char.replace(/[&<>]/g, c => ({ '&': '&', '<': '<', '>': '>' }[c]));
|
|
70
|
-
ascii += `<span style="color:rgb(${data[i]},${data[i + 1]},${data[i + 2]})">${safe}</span>`;
|
|
71
|
-
} else ascii += char;
|
|
72
|
-
}
|
|
73
|
-
ascii += '\n';
|
|
74
|
-
}
|
|
75
|
-
return colored ? ascii + '</pre>' : ascii;
|
|
51
|
+
return asciiFromCanvas(source, { width: cols, charset: chars, invert: inv, format: outputFormat, contrast: gain });
|
|
76
52
|
}
|
|
77
53
|
};
|
|
78
54
|
}
|
|
79
55
|
};
|
|
80
56
|
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Convert any drawable (canvas, image, video frame) to ASCII art. Pure pixel work, no capture:
|
|
60
|
+
* use it on a `stream()` frame, a video element or a canvas of your own.
|
|
61
|
+
* @param {CanvasImageSource & { width: number, height: number }} source
|
|
62
|
+
* @param {Object} [options]
|
|
63
|
+
* @param {number} [options.width=80] - Character width of output
|
|
64
|
+
* @param {string|string[]} [options.charset=' .:-=+*#%@'] - Characters from lightest to darkest
|
|
65
|
+
* @param {boolean} [options.invert=false]
|
|
66
|
+
* @param {'text'|'html'} [options.format='text']
|
|
67
|
+
* @param {number} [options.contrast=0]
|
|
68
|
+
* @returns {string}
|
|
69
|
+
*/
|
|
70
|
+
export function asciiFromCanvas(source, options = {}) {
|
|
71
|
+
const cols = Math.max(1, Math.floor(options.width ?? 80));
|
|
72
|
+
const chars = Array.isArray(options.charset) ? options.charset : Array.from(options.charset ?? ' .:-=+*#%@');
|
|
73
|
+
const inv = options.invert ?? false;
|
|
74
|
+
const colored = options.format === 'html';
|
|
75
|
+
const gain = options.contrast ?? 0;
|
|
76
|
+
const srcW = source.videoWidth || source.naturalWidth || source.width;
|
|
77
|
+
const srcH = source.videoHeight || source.naturalHeight || source.height;
|
|
78
|
+
const rows = Math.max(1, Math.round(cols * srcH / srcW * 0.5));
|
|
79
|
+
const canvas = document.createElement('canvas');
|
|
80
|
+
canvas.width = cols;
|
|
81
|
+
canvas.height = rows;
|
|
82
|
+
const c = canvas.getContext('2d', { willReadFrequently: true });
|
|
83
|
+
// Squash the bitmap, not the SVG viewport: Firefox preserves its viewBox aspect ratio.
|
|
84
|
+
c.drawImage(source, 0, 0, cols, rows);
|
|
85
|
+
const data = c.getImageData(0, 0, cols, rows).data;
|
|
86
|
+
|
|
87
|
+
let ascii = colored ? '<pre style="margin:0;font:7px/1 monospace;white-space:pre;overflow-wrap:normal;max-width:100%;overflow:auto;background:white">' : '';
|
|
88
|
+
for (let y = 0; y < rows; y++) {
|
|
89
|
+
for (let x = 0; x < cols; x++) {
|
|
90
|
+
const i = (y * cols + x) * 4;
|
|
91
|
+
let lum = 1 - (data[i] * 0.299 + data[i + 1] * 0.587 + data[i + 2] * 0.114) / 255;
|
|
92
|
+
lum = Math.max(0, Math.min(1, (1 + gain) * (lum - 0.5) + 0.5));
|
|
93
|
+
if (inv) lum = 1 - lum;
|
|
94
|
+
const ci = Math.min(Math.floor(lum * chars.length), chars.length - 1);
|
|
95
|
+
const char = chars[ci];
|
|
96
|
+
if (colored) {
|
|
97
|
+
const safe = char.replace(/[&<>]/g, ch => ({ '&': '&', '<': '<', '>': '>' }[ch]));
|
|
98
|
+
ascii += `<span style="color:rgb(${data[i]},${data[i + 1]},${data[i + 2]})">${safe}</span>`;
|
|
99
|
+
} else ascii += char;
|
|
100
|
+
}
|
|
101
|
+
ascii += '\n';
|
|
102
|
+
}
|
|
103
|
+
return colored ? ascii + '</pre>' : ascii;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Stream a live element as ASCII: `stream()` keeps a canvas in step with the element and every
|
|
108
|
+
* frame is converted with `asciiFromCanvas()`. Returns the stream handle (`ready`, `stats`, `stop()`).
|
|
109
|
+
* @param {Element} element
|
|
110
|
+
* @param {Object} [options] - ascii options (width, charset, invert, format, contrast) plus stream
|
|
111
|
+
* options (fps, scale, plugins, ...) and `onText(text, frame)`, called after each repaint.
|
|
112
|
+
*/
|
|
113
|
+
export function asciiStream(element, options = {}) {
|
|
114
|
+
const { width, charset, invert, format, contrast, onText, onFrame, ...streamOptions } = options;
|
|
115
|
+
const ascii = { width, charset, invert, format, contrast };
|
|
116
|
+
return stream(element, {
|
|
117
|
+
...streamOptions,
|
|
118
|
+
onFrame(frame) {
|
|
119
|
+
onFrame?.(frame);
|
|
120
|
+
onText?.(asciiFromCanvas(frame.canvas, ascii), frame);
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
}
|