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