asciify-engine 1.0.1 → 1.0.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/dist/index.cjs +537 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -3
- package/dist/index.d.ts +30 -3
- package/dist/index.js +537 -36
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -11,7 +11,9 @@ var CHARSETS = {
|
|
|
11
11
|
binary: "01",
|
|
12
12
|
dots: " \u2801\u2803\u2807\u2847\u28C7\u28E7\u28F7\u28FF",
|
|
13
13
|
letters: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
|
14
|
-
claudeCode: " \u2554\u2557\u255A\u255D\u2551\u2550\u2560\u2563\u2566\u2569\u256C\u2591\u2592\u2593\u2588\u2502\u2500\u250C\u2510\u2514\u2518\u251C\u2524\u252C\u2534\u253C"
|
|
14
|
+
claudeCode: " \u2554\u2557\u255A\u255D\u2551\u2550\u2560\u2563\u2566\u2569\u256C\u2591\u2592\u2593\u2588\u2502\u2500\u250C\u2510\u2514\u2518\u251C\u2524\u252C\u2534\u253C",
|
|
15
|
+
box: " \u25AA\u25FE\u25FC\u25A0\u2588",
|
|
16
|
+
lines: " \u02D7\u2010\u2013\u2014\u2015\u2501"
|
|
15
17
|
};
|
|
16
18
|
var ART_STYLE_PRESETS = {
|
|
17
19
|
classic: {
|
|
@@ -44,6 +46,16 @@ var ART_STYLE_PRESETS = {
|
|
|
44
46
|
renderMode: "ascii",
|
|
45
47
|
charset: CHARSETS.standard,
|
|
46
48
|
colorMode: "matrix"
|
|
49
|
+
},
|
|
50
|
+
box: {
|
|
51
|
+
renderMode: "ascii",
|
|
52
|
+
charset: CHARSETS.box,
|
|
53
|
+
colorMode: "grayscale"
|
|
54
|
+
},
|
|
55
|
+
lines: {
|
|
56
|
+
renderMode: "ascii",
|
|
57
|
+
charset: CHARSETS.lines,
|
|
58
|
+
colorMode: "fullcolor"
|
|
47
59
|
}
|
|
48
60
|
};
|
|
49
61
|
var DEFAULT_OPTIONS = {
|
|
@@ -61,43 +73,44 @@ var DEFAULT_OPTIONS = {
|
|
|
61
73
|
dotSizeRatio: 0.8,
|
|
62
74
|
ditherStrength: 0,
|
|
63
75
|
hoverStrength: 0,
|
|
64
|
-
hoverRadius: 0.
|
|
76
|
+
hoverRadius: 0.2,
|
|
65
77
|
hoverEffect: "spotlight",
|
|
66
78
|
hoverColor: "#ffffff",
|
|
67
|
-
artStyle: "classic"
|
|
79
|
+
artStyle: "classic",
|
|
80
|
+
customText: ""
|
|
68
81
|
};
|
|
69
82
|
var HOVER_PRESETS = {
|
|
70
83
|
none: {
|
|
71
84
|
label: "Off",
|
|
72
|
-
options: { hoverStrength: 0, hoverEffect: "spotlight", hoverRadius: 0.
|
|
85
|
+
options: { hoverStrength: 0, hoverEffect: "spotlight", hoverRadius: 0.2, hoverColor: "#ffffff" }
|
|
73
86
|
},
|
|
74
87
|
subtle: {
|
|
75
88
|
label: "Subtle",
|
|
76
|
-
options: { hoverStrength: 0.
|
|
89
|
+
options: { hoverStrength: 0.25, hoverEffect: "glow", hoverRadius: 0.12, hoverColor: "#ffffff" }
|
|
77
90
|
},
|
|
78
91
|
flashlight: {
|
|
79
92
|
label: "Flashlight",
|
|
80
|
-
options: { hoverStrength:
|
|
93
|
+
options: { hoverStrength: 0.6, hoverEffect: "spotlight", hoverRadius: 0.15, hoverColor: "#fffbe6" }
|
|
81
94
|
},
|
|
82
95
|
magnifier: {
|
|
83
96
|
label: "Magnifier",
|
|
84
|
-
options: { hoverStrength:
|
|
97
|
+
options: { hoverStrength: 0.7, hoverEffect: "magnify", hoverRadius: 0.12, hoverColor: "#ffffff" }
|
|
85
98
|
},
|
|
86
99
|
forceField: {
|
|
87
100
|
label: "Force Field",
|
|
88
|
-
options: { hoverStrength:
|
|
101
|
+
options: { hoverStrength: 0.7, hoverEffect: "repel", hoverRadius: 0.15, hoverColor: "#a0e8ff" }
|
|
89
102
|
},
|
|
90
103
|
neon: {
|
|
91
104
|
label: "Neon",
|
|
92
|
-
options: { hoverStrength:
|
|
105
|
+
options: { hoverStrength: 0.6, hoverEffect: "colorShift", hoverRadius: 0.15, hoverColor: "#d946ef" }
|
|
93
106
|
},
|
|
94
107
|
fire: {
|
|
95
108
|
label: "Fire",
|
|
96
|
-
options: { hoverStrength:
|
|
109
|
+
options: { hoverStrength: 0.7, hoverEffect: "spotlight", hoverRadius: 0.15, hoverColor: "#ff6b2b" }
|
|
97
110
|
},
|
|
98
111
|
ice: {
|
|
99
112
|
label: "Ice",
|
|
100
|
-
options: { hoverStrength:
|
|
113
|
+
options: { hoverStrength: 0.5, hoverEffect: "glow", hoverRadius: 0.15, hoverColor: "#60d5f7" }
|
|
101
114
|
}
|
|
102
115
|
};
|
|
103
116
|
function createOffscreenCanvas(width, height) {
|
|
@@ -118,6 +131,12 @@ function luminanceToChar(lum, charset, invert) {
|
|
|
118
131
|
const index = Math.floor(normalized * (charset.length - 1));
|
|
119
132
|
return charset[Math.max(0, Math.min(charset.length - 1, index))];
|
|
120
133
|
}
|
|
134
|
+
function customTextToChar(lum, text, x, y, cols, invert) {
|
|
135
|
+
const normalized = invert ? 1 - lum / 255 : lum / 255;
|
|
136
|
+
if (normalized < 0.12) return " ";
|
|
137
|
+
const pos = y * cols + x;
|
|
138
|
+
return text[pos % text.length];
|
|
139
|
+
}
|
|
121
140
|
var BAYER_4X4 = [
|
|
122
141
|
[0, 8, 2, 10],
|
|
123
142
|
[12, 4, 14, 6],
|
|
@@ -293,7 +312,7 @@ function imageToAsciiFrame(source, options, targetWidth, targetHeight) {
|
|
|
293
312
|
const lum = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
294
313
|
const adjustedLum = adjustLuminance(lum, options.brightness, options.contrast);
|
|
295
314
|
const ditheredLum = applyDither(adjustedLum, x, y, options.ditherStrength);
|
|
296
|
-
const char = luminanceToChar(ditheredLum, options.charset, options.invert);
|
|
315
|
+
const char = options.customText ? customTextToChar(ditheredLum, options.customText, x, y, cols, options.invert) : luminanceToChar(ditheredLum, options.charset, options.invert);
|
|
297
316
|
row.push({ char, r, g, b, a });
|
|
298
317
|
}
|
|
299
318
|
frame.push(row);
|
|
@@ -472,8 +491,11 @@ function renderFrameToCanvas(ctx, frame, options, canvasWidth, canvasHeight, tim
|
|
|
472
491
|
}
|
|
473
492
|
const cellW = canvasWidth / cols;
|
|
474
493
|
const cellH = canvasHeight / rows;
|
|
494
|
+
const totalCells = rows * cols;
|
|
475
495
|
const hoverIntensity = hoverPos?.intensity ?? 1;
|
|
476
|
-
const
|
|
496
|
+
const animationActive = options.animationStyle !== "none";
|
|
497
|
+
const suppressHover = animationActive && totalCells > 5e3;
|
|
498
|
+
const hoverActive = !suppressHover && !!(hoverPos && options.hoverStrength > 0 && hoverIntensity > 5e-3);
|
|
477
499
|
const hc = options.hoverColor || "#ffffff";
|
|
478
500
|
const hcR = parseInt(hc.slice(1, 3), 16) || 255;
|
|
479
501
|
const hcG = parseInt(hc.slice(3, 5), 16) || 255;
|
|
@@ -482,12 +504,14 @@ function renderFrameToCanvas(ctx, frame, options, canvasWidth, canvasHeight, tim
|
|
|
482
504
|
const acR = parseInt(acHex.substring(0, 2), 16) || 255;
|
|
483
505
|
const acG = parseInt(acHex.substring(2, 4), 16) || 255;
|
|
484
506
|
const acB = parseInt(acHex.substring(4, 6), 16) || 255;
|
|
507
|
+
const radiusScale = totalCells > 3e4 ? 0.25 : totalCells > 15e3 ? 0.4 : totalCells > 5e3 ? 0.6 : 1;
|
|
508
|
+
const effectiveHoverRadius = options.hoverRadius * radiusScale;
|
|
485
509
|
let hoverMinCol = 0, hoverMaxCol = cols, hoverMinRow = 0, hoverMaxRow = rows;
|
|
486
510
|
let hoverPosX = 0, hoverPosY = 0;
|
|
487
511
|
if (hoverActive && hoverPos) {
|
|
488
512
|
hoverPosX = hoverPos.x;
|
|
489
513
|
hoverPosY = hoverPos.y;
|
|
490
|
-
const hoverNormRadius = 0.08 +
|
|
514
|
+
const hoverNormRadius = 0.08 + effectiveHoverRadius * 0.35 + options.hoverStrength * 0.04;
|
|
491
515
|
hoverMinCol = Math.max(0, Math.floor((hoverPosX - hoverNormRadius) * cols) - 1);
|
|
492
516
|
hoverMaxCol = Math.min(cols, Math.ceil((hoverPosX + hoverNormRadius) * cols) + 1);
|
|
493
517
|
hoverMinRow = Math.max(0, Math.floor((hoverPosY - hoverNormRadius) * rows) - 1);
|
|
@@ -498,7 +522,7 @@ function renderFrameToCanvas(ctx, frame, options, canvasWidth, canvasHeight, tim
|
|
|
498
522
|
const noAnimation = animStyle === "none";
|
|
499
523
|
const hoverStrength = options.hoverStrength;
|
|
500
524
|
const hoverEffect = options.hoverEffect;
|
|
501
|
-
const hoverRadiusFactor =
|
|
525
|
+
const hoverRadiusFactor = effectiveHoverRadius;
|
|
502
526
|
const isInverted = options.invert;
|
|
503
527
|
const colorMode = options.colorMode;
|
|
504
528
|
const TWO_PI = Math.PI * 2;
|
|
@@ -564,7 +588,7 @@ function renderFrameToCanvas(ctx, frame, options, canvasWidth, canvasHeight, tim
|
|
|
564
588
|
ctx.fillStyle = color;
|
|
565
589
|
lastFillStyle = color;
|
|
566
590
|
}
|
|
567
|
-
if (radius <=
|
|
591
|
+
if (radius <= 3) {
|
|
568
592
|
const d = radius * 2;
|
|
569
593
|
ctx.fillRect(px - radius, py - radius, d, d);
|
|
570
594
|
} else {
|
|
@@ -577,10 +601,22 @@ function renderFrameToCanvas(ctx, frame, options, canvasWidth, canvasHeight, tim
|
|
|
577
601
|
} else {
|
|
578
602
|
const charAspect = 0.55;
|
|
579
603
|
const fontSize = Math.min(cellW / charAspect, cellH) * 0.9;
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
604
|
+
const useFastRect = fontSize < 6;
|
|
605
|
+
if (!useFastRect) {
|
|
606
|
+
ctx.font = `${fontSize}px "JetBrains Mono", monospace`;
|
|
607
|
+
ctx.textAlign = "center";
|
|
608
|
+
ctx.textBaseline = "middle";
|
|
609
|
+
}
|
|
610
|
+
let charWeights = null;
|
|
611
|
+
if (useFastRect) {
|
|
612
|
+
charWeights = {};
|
|
613
|
+
const cs = options.charset;
|
|
614
|
+
const csLen = cs.length;
|
|
615
|
+
for (let i = 0; i < csLen; i++) {
|
|
616
|
+
charWeights[cs[i]] = Math.max(0.1, (i + 0.3) / csLen);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
const baseTransform = !useFastRect ? ctx.getTransform() : null;
|
|
584
620
|
for (let y = 0; y < rows; y++) {
|
|
585
621
|
const rowData = frame[y];
|
|
586
622
|
for (let x = 0; x < cols; x++) {
|
|
@@ -614,11 +650,6 @@ function renderFrameToCanvas(ctx, frame, options, canvasWidth, canvasHeight, tim
|
|
|
614
650
|
}
|
|
615
651
|
const px = x * cellW + cellW * 0.5 + hoverOffX;
|
|
616
652
|
const py = y * cellH + cellH * 0.5 + hoverOffY;
|
|
617
|
-
const alpha = Math.min(1, cell.a * 0.00392156863 * animMul * (1 + hoverGlow));
|
|
618
|
-
if (alpha !== lastAlpha) {
|
|
619
|
-
ctx.globalAlpha = alpha;
|
|
620
|
-
lastAlpha = alpha;
|
|
621
|
-
}
|
|
622
653
|
let color;
|
|
623
654
|
if (hoverBlend > 0) {
|
|
624
655
|
const rgb = getCellColorRGB(cell, colorMode, acR, acG, acB);
|
|
@@ -629,17 +660,39 @@ function renderFrameToCanvas(ctx, frame, options, canvasWidth, canvasHeight, tim
|
|
|
629
660
|
} else {
|
|
630
661
|
color = getCellColorStr(cell, colorMode, acR, acG, acB);
|
|
631
662
|
}
|
|
632
|
-
if (
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
663
|
+
if (useFastRect) {
|
|
664
|
+
const weight = charWeights[cell.char] ?? 0.5;
|
|
665
|
+
const effAlpha = Math.min(1, cell.a * 0.00392156863 * animMul * (1 + hoverGlow)) * weight;
|
|
666
|
+
if (effAlpha < 0.02) continue;
|
|
667
|
+
if (effAlpha !== lastAlpha) {
|
|
668
|
+
ctx.globalAlpha = effAlpha;
|
|
669
|
+
lastAlpha = effAlpha;
|
|
670
|
+
}
|
|
671
|
+
if (color !== lastFillStyle) {
|
|
672
|
+
ctx.fillStyle = color;
|
|
673
|
+
lastFillStyle = color;
|
|
674
|
+
}
|
|
675
|
+
const rw = cellW * hoverScale;
|
|
676
|
+
const rh = cellH * hoverScale;
|
|
677
|
+
ctx.fillRect(px - rw * 0.5, py - rh * 0.5, rw, rh);
|
|
641
678
|
} else {
|
|
642
|
-
|
|
679
|
+
const alpha = Math.min(1, cell.a * 0.00392156863 * animMul * (1 + hoverGlow));
|
|
680
|
+
if (alpha !== lastAlpha) {
|
|
681
|
+
ctx.globalAlpha = alpha;
|
|
682
|
+
lastAlpha = alpha;
|
|
683
|
+
}
|
|
684
|
+
if (color !== lastFillStyle) {
|
|
685
|
+
ctx.fillStyle = color;
|
|
686
|
+
lastFillStyle = color;
|
|
687
|
+
}
|
|
688
|
+
if (hoverScale !== 1) {
|
|
689
|
+
ctx.translate(px, py);
|
|
690
|
+
ctx.scale(hoverScale, hoverScale);
|
|
691
|
+
ctx.fillText(cell.char, 0, 0);
|
|
692
|
+
ctx.setTransform(baseTransform);
|
|
693
|
+
} else {
|
|
694
|
+
ctx.fillText(cell.char, px, py);
|
|
695
|
+
}
|
|
643
696
|
}
|
|
644
697
|
}
|
|
645
698
|
}
|
|
@@ -862,6 +915,454 @@ function generateAnimatedEmbedCode(frames, options, fps, width, height) {
|
|
|
862
915
|
<!-- /Asciify Animated Embed -->`;
|
|
863
916
|
}
|
|
864
917
|
|
|
918
|
+
// src/webgl-engine.ts
|
|
919
|
+
var VERT_SRC = (
|
|
920
|
+
/* glsl */
|
|
921
|
+
`#version 300 es
|
|
922
|
+
precision highp float;
|
|
923
|
+
precision highp int;
|
|
924
|
+
|
|
925
|
+
// Unit quad corners: (-0.5,-0.5) to (0.5,0.5) - TRIANGLE_STRIP, 4 verts
|
|
926
|
+
in vec2 a_corner;
|
|
927
|
+
|
|
928
|
+
// Layout
|
|
929
|
+
uniform vec2 u_canvasSize;
|
|
930
|
+
uniform vec2 u_gridSize;
|
|
931
|
+
uniform vec2 u_cellSize;
|
|
932
|
+
|
|
933
|
+
// Animation
|
|
934
|
+
uniform float u_time;
|
|
935
|
+
uniform float u_animSpeed;
|
|
936
|
+
uniform int u_animStyle;
|
|
937
|
+
|
|
938
|
+
// Color
|
|
939
|
+
uniform int u_colorMode;
|
|
940
|
+
uniform vec3 u_accentColor;
|
|
941
|
+
uniform int u_invert;
|
|
942
|
+
|
|
943
|
+
// Hover
|
|
944
|
+
uniform float u_hoverStrength;
|
|
945
|
+
uniform float u_hoverIntensity;
|
|
946
|
+
uniform vec2 u_hoverPos;
|
|
947
|
+
uniform float u_hoverRadius;
|
|
948
|
+
uniform int u_hoverEffect;
|
|
949
|
+
uniform vec3 u_hoverColor;
|
|
950
|
+
|
|
951
|
+
// Render mode: 0=rect 1=dots
|
|
952
|
+
uniform int u_renderMode;
|
|
953
|
+
uniform float u_dotSizeRatio;
|
|
954
|
+
|
|
955
|
+
// Cell data: RGBA8 texture (cols x rows)
|
|
956
|
+
// rgb = cell colour, a = source alpha
|
|
957
|
+
uniform sampler2D u_cellTex;
|
|
958
|
+
|
|
959
|
+
out vec4 v_color;
|
|
960
|
+
out float v_alpha;
|
|
961
|
+
out vec2 v_localUv;
|
|
962
|
+
|
|
963
|
+
#define PI 3.141592653589793
|
|
964
|
+
|
|
965
|
+
float ss(float t) { return t * t * (3.0 - 2.0 * t); }
|
|
966
|
+
|
|
967
|
+
float getAnim(float nx, float ny, float cx, float cy, float t) {
|
|
968
|
+
if (u_animStyle == 0) return 1.0;
|
|
969
|
+
if (u_animStyle == 1) { // wave
|
|
970
|
+
return 0.3 + 0.7 * (sin(nx * PI * 4.0 + t * 3.0) * 0.3 + 0.5
|
|
971
|
+
+ sin(ny * PI * 3.0 + t * 2.0) * 0.2);
|
|
972
|
+
}
|
|
973
|
+
if (u_animStyle == 2) { // pulse
|
|
974
|
+
float d = length(vec2(nx - 0.5, ny - 0.5)) * 1.41421;
|
|
975
|
+
return 0.2 + 0.8 * (sin(d * PI * 6.0 - t * 4.0) * 0.5 + 0.5);
|
|
976
|
+
}
|
|
977
|
+
if (u_animStyle == 3) { // rain
|
|
978
|
+
float drop = sin(ny * PI * 8.0 - t * 5.0 + cx * 0.3) * 0.5 + 0.5;
|
|
979
|
+
return 0.1 + 0.9 * drop * (sin(nx * PI * 2.0 + t) * 0.3 + 0.7);
|
|
980
|
+
}
|
|
981
|
+
if (u_animStyle == 4) { // breathe
|
|
982
|
+
return clamp(sin(t * 2.0) * 0.3 + 0.7 + sin((cx + cy) * 0.1 + t) * 0.1, 0.1, 1.0);
|
|
983
|
+
}
|
|
984
|
+
if (u_animStyle == 5) { // sparkle
|
|
985
|
+
float sp = fract(sin(cx * 127.1 + cy * 311.7 + floor(t * 8.0) * 43758.5453) * 43758.5453);
|
|
986
|
+
return sp > 0.7 ? 1.0 : 0.15 + sp * 0.4;
|
|
987
|
+
}
|
|
988
|
+
if (u_animStyle == 6) { // glitch
|
|
989
|
+
float band = floor(cy / max(1.0, u_gridSize.y * 0.05));
|
|
990
|
+
float gv = fract(sin(band * 43.23 + floor(t * 6.0) * 17.89) * 43758.5453);
|
|
991
|
+
if (gv > 0.85) { float fl = sin(t * 30.0 + band) * 0.5 + 0.5; return fl < 0.3 ? 0.0 : fl; }
|
|
992
|
+
return 1.0;
|
|
993
|
+
}
|
|
994
|
+
if (u_animStyle == 7) { // spiral
|
|
995
|
+
float angle = atan(ny - 0.5, nx - 0.5);
|
|
996
|
+
float dist = length(vec2(nx - 0.5, ny - 0.5)) * 1.41421;
|
|
997
|
+
return 0.15 + 0.85 * (sin(angle * 3.0 + dist * PI * 8.0 - t * 3.0) * 0.5 + 0.5);
|
|
998
|
+
}
|
|
999
|
+
if (u_animStyle == 8) { // typewriter
|
|
1000
|
+
float reveal = fract(t * 0.5) * u_gridSize.x * u_gridSize.y * 1.3;
|
|
1001
|
+
float dist = cy * u_gridSize.x + cx - reveal;
|
|
1002
|
+
if (dist > 0.0) return 0.0;
|
|
1003
|
+
return clamp(1.0 + dist / (u_gridSize.x * u_gridSize.y * 0.15), 0.0, 1.0);
|
|
1004
|
+
}
|
|
1005
|
+
if (u_animStyle == 9) { // scatter
|
|
1006
|
+
float d = length(vec2(nx - 0.5, ny - 0.5)) * 1.41421;
|
|
1007
|
+
float phase = sin(t * 1.5) * 0.5 + 0.5;
|
|
1008
|
+
if (d > phase) return max(0.0, 1.0 - (d - phase) * 3.0);
|
|
1009
|
+
return 0.7 + 0.3 * sin(d * 10.0 - t * 2.0);
|
|
1010
|
+
}
|
|
1011
|
+
return 1.0;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
vec3 applyColorMode(vec3 rgb) {
|
|
1015
|
+
float lum = dot(rgb, vec3(0.299, 0.587, 0.114));
|
|
1016
|
+
if (u_colorMode == 0) return vec3(lum);
|
|
1017
|
+
if (u_colorMode == 2) return vec3(0.0, lum, 0.0);
|
|
1018
|
+
if (u_colorMode == 3) return lum * u_accentColor;
|
|
1019
|
+
return rgb;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
void main() {
|
|
1023
|
+
int cols = int(u_gridSize.x);
|
|
1024
|
+
int rows = int(u_gridSize.y);
|
|
1025
|
+
int cx = gl_InstanceID % cols;
|
|
1026
|
+
int cy = gl_InstanceID / cols;
|
|
1027
|
+
|
|
1028
|
+
float fnx = (float(cx) + 0.5) / float(cols);
|
|
1029
|
+
float fny = (float(cy) + 0.5) / float(rows);
|
|
1030
|
+
|
|
1031
|
+
// Sample cell data
|
|
1032
|
+
vec4 cell = texture(u_cellTex, vec2(fnx, fny));
|
|
1033
|
+
// cell.rgb = colour, cell.a = source alpha (0=transparent, 1=opaque)
|
|
1034
|
+
|
|
1035
|
+
// Invisible cells (fully transparent source pixel)
|
|
1036
|
+
if (cell.a < 0.004) {
|
|
1037
|
+
gl_Position = vec4(2.0, 2.0, 0.0, 1.0); v_alpha = 0.0; return;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// Char weight: 0=dark/space .. 1=dense/bright (luminance, matches luminanceToChar ordering)
|
|
1041
|
+
float charW = dot(cell.rgb, vec3(0.299, 0.587, 0.114));
|
|
1042
|
+
|
|
1043
|
+
float cellW = u_cellSize.x;
|
|
1044
|
+
float cellH = u_cellSize.y;
|
|
1045
|
+
|
|
1046
|
+
// Animation
|
|
1047
|
+
float t = u_time * u_animSpeed;
|
|
1048
|
+
float am = getAnim(fnx, fny, float(cx), float(cy), t);
|
|
1049
|
+
if (am < 0.04) {
|
|
1050
|
+
gl_Position = vec4(2.0, 2.0, 0.0, 1.0); v_alpha = 0.0; return;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// Hover
|
|
1054
|
+
float hoverScale = 1.0;
|
|
1055
|
+
vec2 hoverOff = vec2(0.0);
|
|
1056
|
+
float hoverGlow = 0.0;
|
|
1057
|
+
float hoverBlend = 0.0;
|
|
1058
|
+
|
|
1059
|
+
if (u_hoverStrength > 0.0 && u_hoverIntensity > 0.005) {
|
|
1060
|
+
vec2 d = vec2(fnx - u_hoverPos.x, fny - u_hoverPos.y);
|
|
1061
|
+
float r = 0.08 + u_hoverRadius * 0.35 + u_hoverStrength * 0.04;
|
|
1062
|
+
float dist = length(d);
|
|
1063
|
+
if (dist < r) {
|
|
1064
|
+
float tv = 1.0 - dist / r;
|
|
1065
|
+
float e = ss(tv) * u_hoverIntensity;
|
|
1066
|
+
if (u_hoverEffect == 0) { // spotlight
|
|
1067
|
+
hoverScale = 1.0 + e * u_hoverStrength * 1.8;
|
|
1068
|
+
float ang = atan(d.y, d.x);
|
|
1069
|
+
hoverOff = vec2(cos(ang), sin(ang)) * e * e * u_hoverStrength * 0.6 * vec2(cellW, cellH);
|
|
1070
|
+
hoverGlow = e * u_hoverStrength * 0.4;
|
|
1071
|
+
hoverBlend = e * e * u_hoverStrength * 0.25;
|
|
1072
|
+
} else if (u_hoverEffect == 1) { // magnify
|
|
1073
|
+
hoverScale = 1.0 + e * u_hoverStrength * 2.5;
|
|
1074
|
+
hoverGlow = e * u_hoverStrength * 0.15;
|
|
1075
|
+
} else if (u_hoverEffect == 2) { // repel
|
|
1076
|
+
hoverScale = 1.0 + e * u_hoverStrength * 0.3;
|
|
1077
|
+
float ang = atan(d.y, d.x);
|
|
1078
|
+
hoverOff = vec2(cos(ang), sin(ang)) * e * e * u_hoverStrength * 1.2 * vec2(cellW, cellH);
|
|
1079
|
+
} else if (u_hoverEffect == 3) { // glow
|
|
1080
|
+
hoverGlow = e * u_hoverStrength * 0.8;
|
|
1081
|
+
hoverBlend = e * u_hoverStrength * 0.4;
|
|
1082
|
+
} else { // colorShift
|
|
1083
|
+
hoverScale = 1.0 + e * u_hoverStrength * 0.4;
|
|
1084
|
+
hoverGlow = e * u_hoverStrength * 0.2;
|
|
1085
|
+
hoverBlend = e * u_hoverStrength * 0.7;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// Final colour
|
|
1091
|
+
vec3 rgb = (u_invert == 1) ? (1.0 - cell.rgb) : cell.rgb;
|
|
1092
|
+
vec3 baseColor = applyColorMode(rgb);
|
|
1093
|
+
v_color = vec4(mix(baseColor, u_hoverColor, hoverBlend), 1.0);
|
|
1094
|
+
|
|
1095
|
+
// Quad size & alpha
|
|
1096
|
+
vec2 quadSize;
|
|
1097
|
+
float finalAlpha;
|
|
1098
|
+
|
|
1099
|
+
if (u_renderMode == 1) { // dots: radius driven by char weight (bright=big dot)
|
|
1100
|
+
float maxR = min(cellW, cellH) * 0.5 * u_dotSizeRatio;
|
|
1101
|
+
float radius = maxR * charW * am * hoverScale;
|
|
1102
|
+
if (radius < 0.3) { gl_Position = vec4(2.0, 2.0, 0.0, 1.0); v_alpha = 0.0; return; }
|
|
1103
|
+
quadSize = vec2(radius * 2.0);
|
|
1104
|
+
finalAlpha = min(1.0, cell.a * am * (1.0 + hoverGlow));
|
|
1105
|
+
} else { // rect: size proportional to luminance \u2014 dark=tiny, bright=full (ASCII density look)
|
|
1106
|
+
if (charW < 0.01) { gl_Position = vec4(2.0, 2.0, 0.0, 1.0); v_alpha = 0.0; return; }
|
|
1107
|
+
quadSize = vec2(cellW * charW * hoverScale, cellH * charW * hoverScale);
|
|
1108
|
+
finalAlpha = min(1.0, am * (1.0 + hoverGlow));
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
v_alpha = finalAlpha;
|
|
1112
|
+
v_localUv = a_corner;
|
|
1113
|
+
|
|
1114
|
+
// NDC position (Y flipped: WebGL bottom-up, canvas top-down)
|
|
1115
|
+
float px = float(cx) * cellW + cellW * 0.5 + hoverOff.x;
|
|
1116
|
+
float py = float(cy) * cellH + cellH * 0.5 + hoverOff.y;
|
|
1117
|
+
vec2 pos = vec2(px, py) + a_corner * quadSize;
|
|
1118
|
+
vec2 ndc = pos / u_canvasSize * 2.0 - 1.0;
|
|
1119
|
+
ndc.y = -ndc.y;
|
|
1120
|
+
gl_Position = vec4(ndc, 0.0, 1.0);
|
|
1121
|
+
}
|
|
1122
|
+
`
|
|
1123
|
+
);
|
|
1124
|
+
var FRAG_SRC = (
|
|
1125
|
+
/* glsl */
|
|
1126
|
+
`#version 300 es
|
|
1127
|
+
precision mediump float;
|
|
1128
|
+
precision highp int;
|
|
1129
|
+
|
|
1130
|
+
in vec4 v_color;
|
|
1131
|
+
in float v_alpha;
|
|
1132
|
+
in vec2 v_localUv;
|
|
1133
|
+
|
|
1134
|
+
uniform int u_renderMode;
|
|
1135
|
+
|
|
1136
|
+
out vec4 outColor;
|
|
1137
|
+
|
|
1138
|
+
void main() {
|
|
1139
|
+
if (v_alpha < 0.01) discard;
|
|
1140
|
+
if (u_renderMode == 1) {
|
|
1141
|
+
// Dots: circle SDF clip
|
|
1142
|
+
if (length(v_localUv) > 0.5) discard;
|
|
1143
|
+
}
|
|
1144
|
+
outColor = vec4(v_color.rgb, v_alpha);
|
|
1145
|
+
}
|
|
1146
|
+
`
|
|
1147
|
+
);
|
|
1148
|
+
function compileShader(gl, type, src) {
|
|
1149
|
+
const sh = gl.createShader(type);
|
|
1150
|
+
gl.shaderSource(sh, src);
|
|
1151
|
+
gl.compileShader(sh);
|
|
1152
|
+
if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
|
|
1153
|
+
const info = gl.getShaderInfoLog(sh) ?? "unknown";
|
|
1154
|
+
gl.deleteShader(sh);
|
|
1155
|
+
throw new Error(`Shader compile error: ${info}`);
|
|
1156
|
+
}
|
|
1157
|
+
return sh;
|
|
1158
|
+
}
|
|
1159
|
+
function linkProgram(gl, vert, frag) {
|
|
1160
|
+
const prog = gl.createProgram();
|
|
1161
|
+
gl.attachShader(prog, vert);
|
|
1162
|
+
gl.attachShader(prog, frag);
|
|
1163
|
+
gl.linkProgram(prog);
|
|
1164
|
+
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
|
|
1165
|
+
const info = gl.getProgramInfoLog(prog) ?? "unknown";
|
|
1166
|
+
gl.deleteProgram(prog);
|
|
1167
|
+
throw new Error(`Program link error: ${info}`);
|
|
1168
|
+
}
|
|
1169
|
+
return prog;
|
|
1170
|
+
}
|
|
1171
|
+
function makeTexture(gl, filter = gl.NEAREST) {
|
|
1172
|
+
const t = gl.createTexture();
|
|
1173
|
+
gl.bindTexture(gl.TEXTURE_2D, t);
|
|
1174
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
|
|
1175
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
|
|
1176
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
1177
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
1178
|
+
return t;
|
|
1179
|
+
}
|
|
1180
|
+
var ANIM_STYLES = ["none", "wave", "pulse", "rain", "breathe", "sparkle", "glitch", "spiral", "typewriter", "scatter"];
|
|
1181
|
+
var HOVER_EFFECTS = ["spotlight", "magnify", "repel", "glow", "colorShift"];
|
|
1182
|
+
var COLOR_MODES = ["grayscale", "fullcolor", "matrix", "accent"];
|
|
1183
|
+
function tryCreateWebGLRenderer(canvas) {
|
|
1184
|
+
const glOrNull = canvas.getContext("webgl2", {
|
|
1185
|
+
alpha: true,
|
|
1186
|
+
antialias: false,
|
|
1187
|
+
premultipliedAlpha: false,
|
|
1188
|
+
preserveDrawingBuffer: false
|
|
1189
|
+
});
|
|
1190
|
+
if (!glOrNull) return null;
|
|
1191
|
+
const gl = glOrNull;
|
|
1192
|
+
try {
|
|
1193
|
+
let render2 = function(frame, options, displayW, displayH, time, hoverPos) {
|
|
1194
|
+
const rows = frame.length;
|
|
1195
|
+
if (rows === 0) return;
|
|
1196
|
+
const cols = frame[0].length;
|
|
1197
|
+
if (cols === 0) return;
|
|
1198
|
+
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
|
|
1199
|
+
let hasTransparency = false;
|
|
1200
|
+
const sy = Math.max(1, rows >> 2), sx = Math.max(1, cols >> 2);
|
|
1201
|
+
outer: for (let iy = 0; iy < rows; iy += sy)
|
|
1202
|
+
for (let ix = 0; ix < cols; ix += sx)
|
|
1203
|
+
if (frame[iy][ix].a < 200) {
|
|
1204
|
+
hasTransparency = true;
|
|
1205
|
+
break outer;
|
|
1206
|
+
}
|
|
1207
|
+
gl.clearColor(
|
|
1208
|
+
hasTransparency ? 0 : 0.0392,
|
|
1209
|
+
hasTransparency ? 0 : 0.0392,
|
|
1210
|
+
hasTransparency ? 0 : 0.0392,
|
|
1211
|
+
hasTransparency ? 0 : 1
|
|
1212
|
+
);
|
|
1213
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
1214
|
+
if (cols !== lastCols || rows !== lastRows) {
|
|
1215
|
+
cellBuf = new Uint8Array(cols * rows * 4);
|
|
1216
|
+
lastCols = cols;
|
|
1217
|
+
lastRows = rows;
|
|
1218
|
+
cellDataDirty = true;
|
|
1219
|
+
}
|
|
1220
|
+
if (cellDataDirty) {
|
|
1221
|
+
for (let y = 0; y < rows; y++) {
|
|
1222
|
+
const row = frame[y];
|
|
1223
|
+
for (let x = 0; x < cols; x++) {
|
|
1224
|
+
const cell = row[x];
|
|
1225
|
+
const i = (y * cols + x) * 4;
|
|
1226
|
+
cellBuf[i] = cell.r;
|
|
1227
|
+
cellBuf[i + 1] = cell.g;
|
|
1228
|
+
cellBuf[i + 2] = cell.b;
|
|
1229
|
+
cellBuf[i + 3] = cell.a;
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
1233
|
+
gl.bindTexture(gl.TEXTURE_2D, cellTex);
|
|
1234
|
+
gl.texImage2D(
|
|
1235
|
+
gl.TEXTURE_2D,
|
|
1236
|
+
0,
|
|
1237
|
+
gl.RGBA,
|
|
1238
|
+
cols,
|
|
1239
|
+
rows,
|
|
1240
|
+
0,
|
|
1241
|
+
gl.RGBA,
|
|
1242
|
+
gl.UNSIGNED_BYTE,
|
|
1243
|
+
cellBuf
|
|
1244
|
+
);
|
|
1245
|
+
cellDataDirty = false;
|
|
1246
|
+
}
|
|
1247
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
1248
|
+
gl.bindTexture(gl.TEXTURE_2D, cellTex);
|
|
1249
|
+
gl.useProgram(prog);
|
|
1250
|
+
gl.bindVertexArray(vao);
|
|
1251
|
+
const cellW = displayW / cols;
|
|
1252
|
+
const cellH = displayH / rows;
|
|
1253
|
+
gl.uniform2f(u.canvasSize, displayW, displayH);
|
|
1254
|
+
gl.uniform2f(u.gridSize, cols, rows);
|
|
1255
|
+
gl.uniform2f(u.cellSize, cellW, cellH);
|
|
1256
|
+
gl.uniform1f(u.time, time);
|
|
1257
|
+
gl.uniform1f(u.animSpeed, options.animationSpeed);
|
|
1258
|
+
gl.uniform1i(u.animStyle, ANIM_STYLES.indexOf(options.animationStyle));
|
|
1259
|
+
gl.uniform1i(u.colorMode, COLOR_MODES.indexOf(options.colorMode));
|
|
1260
|
+
const acHex = (options.accentColor || "#ffffff").replace("#", "");
|
|
1261
|
+
gl.uniform3f(
|
|
1262
|
+
u.accentColor,
|
|
1263
|
+
parseInt(acHex.slice(0, 2), 16) / 255,
|
|
1264
|
+
parseInt(acHex.slice(2, 4), 16) / 255,
|
|
1265
|
+
parseInt(acHex.slice(4, 6), 16) / 255
|
|
1266
|
+
);
|
|
1267
|
+
gl.uniform1i(u.invert, options.invert ? 1 : 0);
|
|
1268
|
+
const totalCells = rows * cols;
|
|
1269
|
+
const animActive = options.animationStyle !== "none";
|
|
1270
|
+
const suppressHover = animActive && totalCells > 5e3;
|
|
1271
|
+
const hoverActive = !suppressHover && !!hoverPos && options.hoverStrength > 0;
|
|
1272
|
+
const radiusScale = totalCells > 3e4 ? 0.25 : totalCells > 15e3 ? 0.4 : totalCells > 5e3 ? 0.6 : 1;
|
|
1273
|
+
gl.uniform1f(u.hoverStrength, hoverActive ? options.hoverStrength : 0);
|
|
1274
|
+
gl.uniform1f(u.hoverIntensity, hoverActive ? hoverPos?.intensity ?? 0 : 0);
|
|
1275
|
+
gl.uniform2f(u.hoverPos, hoverPos?.x ?? 0.5, hoverPos?.y ?? 0.5);
|
|
1276
|
+
gl.uniform1f(u.hoverRadius, options.hoverRadius * radiusScale);
|
|
1277
|
+
gl.uniform1i(u.hoverEffect, HOVER_EFFECTS.indexOf(options.hoverEffect));
|
|
1278
|
+
const hc = options.hoverColor || "#ffffff";
|
|
1279
|
+
gl.uniform3f(
|
|
1280
|
+
u.hoverColor,
|
|
1281
|
+
parseInt(hc.slice(1, 3), 16) / 255,
|
|
1282
|
+
parseInt(hc.slice(3, 5), 16) / 255,
|
|
1283
|
+
parseInt(hc.slice(5, 7), 16) / 255
|
|
1284
|
+
);
|
|
1285
|
+
gl.uniform1i(u.renderMode, options.renderMode === "dots" ? 1 : 0);
|
|
1286
|
+
gl.uniform1f(u.dotSizeRatio, options.dotSizeRatio);
|
|
1287
|
+
gl.uniform1i(u.cellTexU, 0);
|
|
1288
|
+
gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, cols * rows);
|
|
1289
|
+
gl.bindVertexArray(null);
|
|
1290
|
+
};
|
|
1291
|
+
var render = render2;
|
|
1292
|
+
const vert = compileShader(gl, gl.VERTEX_SHADER, VERT_SRC);
|
|
1293
|
+
const frag = compileShader(gl, gl.FRAGMENT_SHADER, FRAG_SRC);
|
|
1294
|
+
const prog = linkProgram(gl, vert, frag);
|
|
1295
|
+
gl.deleteShader(vert);
|
|
1296
|
+
gl.deleteShader(frag);
|
|
1297
|
+
const vao = gl.createVertexArray();
|
|
1298
|
+
gl.bindVertexArray(vao);
|
|
1299
|
+
const corners = new Float32Array([-0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5]);
|
|
1300
|
+
const vbo = gl.createBuffer();
|
|
1301
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
|
|
1302
|
+
gl.bufferData(gl.ARRAY_BUFFER, corners, gl.STATIC_DRAW);
|
|
1303
|
+
const aCorner = gl.getAttribLocation(prog, "a_corner");
|
|
1304
|
+
gl.enableVertexAttribArray(aCorner);
|
|
1305
|
+
gl.vertexAttribPointer(aCorner, 2, gl.FLOAT, false, 0, 0);
|
|
1306
|
+
gl.bindVertexArray(null);
|
|
1307
|
+
const cellTex = makeTexture(gl);
|
|
1308
|
+
const g = (n) => gl.getUniformLocation(prog, n);
|
|
1309
|
+
const u = {
|
|
1310
|
+
canvasSize: g("u_canvasSize"),
|
|
1311
|
+
gridSize: g("u_gridSize"),
|
|
1312
|
+
cellSize: g("u_cellSize"),
|
|
1313
|
+
time: g("u_time"),
|
|
1314
|
+
animSpeed: g("u_animSpeed"),
|
|
1315
|
+
animStyle: g("u_animStyle"),
|
|
1316
|
+
colorMode: g("u_colorMode"),
|
|
1317
|
+
accentColor: g("u_accentColor"),
|
|
1318
|
+
invert: g("u_invert"),
|
|
1319
|
+
hoverStrength: g("u_hoverStrength"),
|
|
1320
|
+
hoverIntensity: g("u_hoverIntensity"),
|
|
1321
|
+
hoverPos: g("u_hoverPos"),
|
|
1322
|
+
hoverRadius: g("u_hoverRadius"),
|
|
1323
|
+
hoverEffect: g("u_hoverEffect"),
|
|
1324
|
+
hoverColor: g("u_hoverColor"),
|
|
1325
|
+
renderMode: g("u_renderMode"),
|
|
1326
|
+
dotSizeRatio: g("u_dotSizeRatio"),
|
|
1327
|
+
cellTexU: g("u_cellTex")
|
|
1328
|
+
};
|
|
1329
|
+
let lastCols = 0;
|
|
1330
|
+
let lastRows = 0;
|
|
1331
|
+
let cellBuf = new Uint8Array(0);
|
|
1332
|
+
let cellDataDirty = true;
|
|
1333
|
+
gl.enable(gl.BLEND);
|
|
1334
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
1335
|
+
return {
|
|
1336
|
+
render: render2,
|
|
1337
|
+
notifyDirty() {
|
|
1338
|
+
cellDataDirty = true;
|
|
1339
|
+
},
|
|
1340
|
+
destroy() {
|
|
1341
|
+
gl.deleteProgram(prog);
|
|
1342
|
+
gl.deleteVertexArray(vao);
|
|
1343
|
+
gl.deleteBuffer(vbo);
|
|
1344
|
+
gl.deleteTexture(cellTex);
|
|
1345
|
+
}
|
|
1346
|
+
};
|
|
1347
|
+
} catch (err) {
|
|
1348
|
+
console.error("[asciify] WebGL renderer setup FAILED (returning stub):", err);
|
|
1349
|
+
const stub = {
|
|
1350
|
+
render() {
|
|
1351
|
+
try {
|
|
1352
|
+
gl.clearColor(0.039, 0.039, 0.039, 1);
|
|
1353
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
1354
|
+
} catch {
|
|
1355
|
+
}
|
|
1356
|
+
},
|
|
1357
|
+
notifyDirty() {
|
|
1358
|
+
},
|
|
1359
|
+
destroy() {
|
|
1360
|
+
}
|
|
1361
|
+
};
|
|
1362
|
+
return stub;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
|
|
865
1366
|
exports.ART_STYLE_PRESETS = ART_STYLE_PRESETS;
|
|
866
1367
|
exports.CHARSETS = CHARSETS;
|
|
867
1368
|
exports.DEFAULT_OPTIONS = DEFAULT_OPTIONS;
|
|
@@ -871,6 +1372,7 @@ exports.generateEmbedCode = generateEmbedCode;
|
|
|
871
1372
|
exports.gifToAsciiFrames = gifToAsciiFrames;
|
|
872
1373
|
exports.imageToAsciiFrame = imageToAsciiFrame;
|
|
873
1374
|
exports.renderFrameToCanvas = renderFrameToCanvas;
|
|
1375
|
+
exports.tryCreateWebGLRenderer = tryCreateWebGLRenderer;
|
|
874
1376
|
exports.videoToAsciiFrames = videoToAsciiFrames;
|
|
875
1377
|
//# sourceMappingURL=index.cjs.map
|
|
876
1378
|
//# sourceMappingURL=index.cjs.map
|