instantshader 0.7.0 → 0.8.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/README.md +38 -20
- package/dist/index.d.ts +16 -1
- package/dist/index.js +719 -203
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -254,7 +254,7 @@ vec2 seedOffset() {
|
|
|
254
254
|
|
|
255
255
|
//#endregion
|
|
256
256
|
//#region src/shaders/flow.ts
|
|
257
|
-
const FRAGMENT$
|
|
257
|
+
const FRAGMENT$11 = `
|
|
258
258
|
uniform float u_scale;
|
|
259
259
|
uniform float u_curl;
|
|
260
260
|
uniform float u_drift;
|
|
@@ -391,7 +391,7 @@ void main() {
|
|
|
391
391
|
const flow = {
|
|
392
392
|
id: "flow",
|
|
393
393
|
label: "Flow",
|
|
394
|
-
fragment: FRAGMENT$
|
|
394
|
+
fragment: FRAGMENT$11,
|
|
395
395
|
params: [
|
|
396
396
|
{
|
|
397
397
|
key: "scale",
|
|
@@ -447,7 +447,7 @@ const flow = {
|
|
|
447
447
|
|
|
448
448
|
//#endregion
|
|
449
449
|
//#region src/shaders/beam.ts
|
|
450
|
-
const FRAGMENT$
|
|
450
|
+
const FRAGMENT$10 = `
|
|
451
451
|
uniform float u_scale;
|
|
452
452
|
uniform float u_width;
|
|
453
453
|
uniform float u_glow;
|
|
@@ -701,7 +701,7 @@ void main() {
|
|
|
701
701
|
const beam = {
|
|
702
702
|
id: "beam",
|
|
703
703
|
label: "Beam",
|
|
704
|
-
fragment: FRAGMENT$
|
|
704
|
+
fragment: FRAGMENT$10,
|
|
705
705
|
params: [
|
|
706
706
|
{
|
|
707
707
|
key: "scale",
|
|
@@ -709,7 +709,7 @@ const beam = {
|
|
|
709
709
|
min: .5,
|
|
710
710
|
max: 2,
|
|
711
711
|
step: .05,
|
|
712
|
-
default:
|
|
712
|
+
default: .75
|
|
713
713
|
},
|
|
714
714
|
{
|
|
715
715
|
key: "width",
|
|
@@ -717,7 +717,7 @@ const beam = {
|
|
|
717
717
|
min: .04,
|
|
718
718
|
max: .6,
|
|
719
719
|
step: .005,
|
|
720
|
-
default: .
|
|
720
|
+
default: .4
|
|
721
721
|
},
|
|
722
722
|
{
|
|
723
723
|
key: "glow",
|
|
@@ -757,7 +757,7 @@ const beam = {
|
|
|
757
757
|
|
|
758
758
|
//#endregion
|
|
759
759
|
//#region src/shaders/bloom.ts
|
|
760
|
-
const FRAGMENT$
|
|
760
|
+
const FRAGMENT$9 = `
|
|
761
761
|
uniform float u_scale;
|
|
762
762
|
uniform float u_petals;
|
|
763
763
|
uniform float u_pinch;
|
|
@@ -916,7 +916,7 @@ void main() {
|
|
|
916
916
|
const bloom = {
|
|
917
917
|
id: "bloom",
|
|
918
918
|
label: "Bloom",
|
|
919
|
-
fragment: FRAGMENT$
|
|
919
|
+
fragment: FRAGMENT$9,
|
|
920
920
|
params: [
|
|
921
921
|
{
|
|
922
922
|
key: "scale",
|
|
@@ -990,7 +990,7 @@ const bloom = {
|
|
|
990
990
|
|
|
991
991
|
//#endregion
|
|
992
992
|
//#region src/shaders/halo.ts
|
|
993
|
-
const FRAGMENT$
|
|
993
|
+
const FRAGMENT$8 = `
|
|
994
994
|
uniform float u_radius;
|
|
995
995
|
uniform float u_x;
|
|
996
996
|
uniform float u_y;
|
|
@@ -1103,7 +1103,7 @@ void main() {
|
|
|
1103
1103
|
const halo = {
|
|
1104
1104
|
id: "halo",
|
|
1105
1105
|
label: "Halo",
|
|
1106
|
-
fragment: FRAGMENT$
|
|
1106
|
+
fragment: FRAGMENT$8,
|
|
1107
1107
|
params: [
|
|
1108
1108
|
{
|
|
1109
1109
|
key: "radius",
|
|
@@ -1178,7 +1178,7 @@ const halo = {
|
|
|
1178
1178
|
|
|
1179
1179
|
//#endregion
|
|
1180
1180
|
//#region src/shaders/strata.ts
|
|
1181
|
-
const FRAGMENT$
|
|
1181
|
+
const FRAGMENT$7 = `
|
|
1182
1182
|
uniform float u_scale;
|
|
1183
1183
|
uniform float u_layers;
|
|
1184
1184
|
uniform float u_warp;
|
|
@@ -1283,7 +1283,7 @@ void main() {
|
|
|
1283
1283
|
const strata = {
|
|
1284
1284
|
id: "strata",
|
|
1285
1285
|
label: "Strata",
|
|
1286
|
-
fragment: FRAGMENT$
|
|
1286
|
+
fragment: FRAGMENT$7,
|
|
1287
1287
|
params: [
|
|
1288
1288
|
{
|
|
1289
1289
|
key: "scale",
|
|
@@ -1375,7 +1375,7 @@ const strata = {
|
|
|
1375
1375
|
|
|
1376
1376
|
//#endregion
|
|
1377
1377
|
//#region src/shaders/dune.ts
|
|
1378
|
-
const FRAGMENT$
|
|
1378
|
+
const FRAGMENT$6 = `
|
|
1379
1379
|
uniform float u_layers;
|
|
1380
1380
|
uniform float u_swell;
|
|
1381
1381
|
uniform float u_waves;
|
|
@@ -1452,7 +1452,7 @@ void main() {
|
|
|
1452
1452
|
const dune = {
|
|
1453
1453
|
id: "dune",
|
|
1454
1454
|
label: "Dune",
|
|
1455
|
-
fragment: FRAGMENT$
|
|
1455
|
+
fragment: FRAGMENT$6,
|
|
1456
1456
|
params: [
|
|
1457
1457
|
{
|
|
1458
1458
|
key: "layers",
|
|
@@ -1526,7 +1526,7 @@ const dune = {
|
|
|
1526
1526
|
|
|
1527
1527
|
//#endregion
|
|
1528
1528
|
//#region src/shaders/whorl.ts
|
|
1529
|
-
const FRAGMENT$
|
|
1529
|
+
const FRAGMENT$5 = `
|
|
1530
1530
|
uniform float u_blades;
|
|
1531
1531
|
uniform float u_twist;
|
|
1532
1532
|
uniform float u_depth;
|
|
@@ -1579,7 +1579,7 @@ void main() {
|
|
|
1579
1579
|
const whorl = {
|
|
1580
1580
|
id: "whorl",
|
|
1581
1581
|
label: "Whorl",
|
|
1582
|
-
fragment: FRAGMENT$
|
|
1582
|
+
fragment: FRAGMENT$5,
|
|
1583
1583
|
params: [
|
|
1584
1584
|
{
|
|
1585
1585
|
key: "blades",
|
|
@@ -1619,7 +1619,7 @@ const whorl = {
|
|
|
1619
1619
|
min: 0,
|
|
1620
1620
|
max: 1,
|
|
1621
1621
|
step: .01,
|
|
1622
|
-
default:
|
|
1622
|
+
default: 0
|
|
1623
1623
|
},
|
|
1624
1624
|
{
|
|
1625
1625
|
key: "x",
|
|
@@ -1662,7 +1662,7 @@ const whorl = {
|
|
|
1662
1662
|
|
|
1663
1663
|
//#endregion
|
|
1664
1664
|
//#region src/shaders/silk.ts
|
|
1665
|
-
const FRAGMENT$
|
|
1665
|
+
const FRAGMENT$4 = `
|
|
1666
1666
|
uniform float u_scale;
|
|
1667
1667
|
uniform float u_folds;
|
|
1668
1668
|
uniform float u_depth;
|
|
@@ -1751,7 +1751,7 @@ void main() {
|
|
|
1751
1751
|
const silk = {
|
|
1752
1752
|
id: "silk",
|
|
1753
1753
|
label: "Silk",
|
|
1754
|
-
fragment: FRAGMENT$
|
|
1754
|
+
fragment: FRAGMENT$4,
|
|
1755
1755
|
params: [
|
|
1756
1756
|
{
|
|
1757
1757
|
key: "scale",
|
|
@@ -1825,7 +1825,7 @@ const silk = {
|
|
|
1825
1825
|
|
|
1826
1826
|
//#endregion
|
|
1827
1827
|
//#region src/shaders/nacre.ts
|
|
1828
|
-
const FRAGMENT$
|
|
1828
|
+
const FRAGMENT$3 = `
|
|
1829
1829
|
uniform float u_scale;
|
|
1830
1830
|
uniform float u_flow;
|
|
1831
1831
|
uniform float u_crease;
|
|
@@ -1902,7 +1902,7 @@ void main() {
|
|
|
1902
1902
|
const nacre = {
|
|
1903
1903
|
id: "nacre",
|
|
1904
1904
|
label: "Nacre",
|
|
1905
|
-
fragment: FRAGMENT$
|
|
1905
|
+
fragment: FRAGMENT$3,
|
|
1906
1906
|
params: [
|
|
1907
1907
|
{
|
|
1908
1908
|
key: "scale",
|
|
@@ -1976,7 +1976,7 @@ const nacre = {
|
|
|
1976
1976
|
|
|
1977
1977
|
//#endregion
|
|
1978
1978
|
//#region src/shaders/burst.ts
|
|
1979
|
-
const FRAGMENT$
|
|
1979
|
+
const FRAGMENT$2 = `
|
|
1980
1980
|
uniform float u_rays;
|
|
1981
1981
|
uniform float u_sharp;
|
|
1982
1982
|
uniform float u_glow;
|
|
@@ -2040,7 +2040,7 @@ void main() {
|
|
|
2040
2040
|
const burst = {
|
|
2041
2041
|
id: "burst",
|
|
2042
2042
|
label: "Burst",
|
|
2043
|
-
fragment: FRAGMENT$
|
|
2043
|
+
fragment: FRAGMENT$2,
|
|
2044
2044
|
params: [
|
|
2045
2045
|
{
|
|
2046
2046
|
key: "rays",
|
|
@@ -2114,7 +2114,7 @@ const burst = {
|
|
|
2114
2114
|
|
|
2115
2115
|
//#endregion
|
|
2116
2116
|
//#region src/shaders/glint.ts
|
|
2117
|
-
const FRAGMENT = `
|
|
2117
|
+
const FRAGMENT$1 = `
|
|
2118
2118
|
uniform float u_size;
|
|
2119
2119
|
uniform float u_density;
|
|
2120
2120
|
uniform float u_bend;
|
|
@@ -2237,7 +2237,7 @@ void main() {
|
|
|
2237
2237
|
const glint = {
|
|
2238
2238
|
id: "glint",
|
|
2239
2239
|
label: "Glint",
|
|
2240
|
-
fragment: FRAGMENT,
|
|
2240
|
+
fragment: FRAGMENT$1,
|
|
2241
2241
|
params: [
|
|
2242
2242
|
{
|
|
2243
2243
|
key: "size",
|
|
@@ -2300,94 +2300,6 @@ const glint = {
|
|
|
2300
2300
|
}
|
|
2301
2301
|
};
|
|
2302
2302
|
|
|
2303
|
-
//#endregion
|
|
2304
|
-
//#region src/effects/pixelate.ts
|
|
2305
|
-
const CELL_FRAGMENT$3 = `
|
|
2306
|
-
uniform float u_levels;
|
|
2307
|
-
|
|
2308
|
-
void main() {
|
|
2309
|
-
vec3 c = cellSource().rgb;
|
|
2310
|
-
// Posterize per channel. Rounds to the nearest level, so black and white
|
|
2311
|
-
// stay put. levels = 1 would divide by zero and means nothing; 0 is "off".
|
|
2312
|
-
if (u_levels >= 2.0) {
|
|
2313
|
-
float n = u_levels - 1.0;
|
|
2314
|
-
c = floor(c * n + 0.5) / n;
|
|
2315
|
-
}
|
|
2316
|
-
gl_FragColor = vec4(c, 1.0);
|
|
2317
|
-
}
|
|
2318
|
-
`;
|
|
2319
|
-
const DRAW_FRAGMENT$2 = `
|
|
2320
|
-
uniform float u_gap;
|
|
2321
|
-
uniform vec3 u_gapColor;
|
|
2322
|
-
|
|
2323
|
-
void main() {
|
|
2324
|
-
vec3 color = coverage();
|
|
2325
|
-
if (u_gap > 0.0) {
|
|
2326
|
-
// A gap line is u_gap of a cell wide, centred on every cell boundary.
|
|
2327
|
-
// Its weight is the exact share of this output pixel it covers, and the
|
|
2328
|
-
// blend happens in linear light, for the same reason coverage() works
|
|
2329
|
-
// that way: a smoothstep edge in gamma space draws sub-pixel lines far
|
|
2330
|
-
// too heavy, so a small preview would look darker than its own export.
|
|
2331
|
-
vec2 px = cellPx();
|
|
2332
|
-
vec2 d = min(cellLocal(), 1.0 - cellLocal()) * px; // px to nearest boundary
|
|
2333
|
-
vec2 hw = 0.5 * u_gap * px; // line half-width in px
|
|
2334
|
-
vec2 cov = clamp(min(hw, d + 0.5) - max(-hw, d - 0.5), 0.0, 1.0);
|
|
2335
|
-
float m = 1.0 - (1.0 - cov.x) * (1.0 - cov.y);
|
|
2336
|
-
color = toSrgb(mix(toLinear(color), toLinear(u_gapColor), m));
|
|
2337
|
-
}
|
|
2338
|
-
gl_FragColor = vec4(color, 1.0);
|
|
2339
|
-
}
|
|
2340
|
-
`;
|
|
2341
|
-
const pixelate = {
|
|
2342
|
-
id: "pixelate",
|
|
2343
|
-
label: "Pixelate",
|
|
2344
|
-
fragment: DRAW_FRAGMENT$2,
|
|
2345
|
-
grid: {
|
|
2346
|
-
cell: (p) => [p.size, p.size],
|
|
2347
|
-
fragment: CELL_FRAGMENT$3
|
|
2348
|
-
},
|
|
2349
|
-
params: [
|
|
2350
|
-
{
|
|
2351
|
-
key: "size",
|
|
2352
|
-
label: "Size",
|
|
2353
|
-
min: 2,
|
|
2354
|
-
max: 160,
|
|
2355
|
-
step: 1,
|
|
2356
|
-
default: 24
|
|
2357
|
-
},
|
|
2358
|
-
{
|
|
2359
|
-
key: "levels",
|
|
2360
|
-
label: "Levels",
|
|
2361
|
-
min: 0,
|
|
2362
|
-
max: 16,
|
|
2363
|
-
step: 1,
|
|
2364
|
-
default: 0
|
|
2365
|
-
},
|
|
2366
|
-
{
|
|
2367
|
-
key: "gap",
|
|
2368
|
-
label: "Gap",
|
|
2369
|
-
min: 0,
|
|
2370
|
-
max: .4,
|
|
2371
|
-
step: .01,
|
|
2372
|
-
default: 0
|
|
2373
|
-
},
|
|
2374
|
-
{
|
|
2375
|
-
key: "gapColor",
|
|
2376
|
-
label: "Gap color",
|
|
2377
|
-
type: "color",
|
|
2378
|
-
default: "#000000"
|
|
2379
|
-
}
|
|
2380
|
-
],
|
|
2381
|
-
randomParams(rand) {
|
|
2382
|
-
return {
|
|
2383
|
-
size: Math.round(10 + rand() * 50),
|
|
2384
|
-
levels: rand() < .5 ? 0 : Math.round(3 + rand() * 5),
|
|
2385
|
-
gap: rand() < .6 ? 0 : .05 + rand() * .15,
|
|
2386
|
-
gapColor: "#000000"
|
|
2387
|
-
};
|
|
2388
|
-
}
|
|
2389
|
-
};
|
|
2390
|
-
|
|
2391
2303
|
//#endregion
|
|
2392
2304
|
//#region src/effects/chunks.ts
|
|
2393
2305
|
/** sRGB transfer functions. Blending and averaging must happen in linear
|
|
@@ -2608,8 +2520,7 @@ vec3 colorize(float tone, vec3 src) {
|
|
|
2608
2520
|
return palette(tone);
|
|
2609
2521
|
}
|
|
2610
2522
|
`;
|
|
2611
|
-
/** The param defs that go with COLOR_MODE.
|
|
2612
|
-
* where paper is a visible ground in every mode (ASCII, Halftone). */
|
|
2523
|
+
/** The param defs that go with COLOR_MODE. */
|
|
2613
2524
|
function colorModeParams(opts) {
|
|
2614
2525
|
return [
|
|
2615
2526
|
{
|
|
@@ -2623,7 +2534,7 @@ function colorModeParams(opts) {
|
|
|
2623
2534
|
},
|
|
2624
2535
|
{
|
|
2625
2536
|
value: "duotone",
|
|
2626
|
-
label: "Duotone"
|
|
2537
|
+
label: opts.singleColour ? "Single colour" : "Duotone"
|
|
2627
2538
|
},
|
|
2628
2539
|
{
|
|
2629
2540
|
value: "palette",
|
|
@@ -2634,7 +2545,7 @@ function colorModeParams(opts) {
|
|
|
2634
2545
|
},
|
|
2635
2546
|
{
|
|
2636
2547
|
key: "ink",
|
|
2637
|
-
label: "Ink",
|
|
2548
|
+
label: opts.singleColour ? "Colour" : "Ink",
|
|
2638
2549
|
type: "color",
|
|
2639
2550
|
default: opts.ink ?? "#111111",
|
|
2640
2551
|
when: {
|
|
@@ -2647,10 +2558,10 @@ function colorModeParams(opts) {
|
|
|
2647
2558
|
label: "Paper",
|
|
2648
2559
|
type: "color",
|
|
2649
2560
|
default: opts.paper ?? "#f4f1ea",
|
|
2650
|
-
|
|
2561
|
+
when: opts.paperWhen ?? {
|
|
2651
2562
|
key: "colorMode",
|
|
2652
2563
|
in: ["duotone"]
|
|
2653
|
-
}
|
|
2564
|
+
}
|
|
2654
2565
|
},
|
|
2655
2566
|
{
|
|
2656
2567
|
key: "invert",
|
|
@@ -2660,6 +2571,341 @@ function colorModeParams(opts) {
|
|
|
2660
2571
|
}
|
|
2661
2572
|
];
|
|
2662
2573
|
}
|
|
2574
|
+
/**
|
|
2575
|
+
* Blend modes, W3C Compositing and Blending Level 1, computed on
|
|
2576
|
+
* gamma-encoded sRGB like CSS and every image editor. b is the backdrop, s
|
|
2577
|
+
* the source (the mark or the filter's colour). Overlay is hard light with
|
|
2578
|
+
* the roles swapped. Color dodge: 0 where b is 0, else min(1, b / (1 - s)).
|
|
2579
|
+
*/
|
|
2580
|
+
const BLEND = `
|
|
2581
|
+
uniform float u_blend;
|
|
2582
|
+
vec3 blendScreen(vec3 b, vec3 s) { return b + s - b * s; }
|
|
2583
|
+
vec3 blendHardLight(vec3 b, vec3 s) {
|
|
2584
|
+
return mix(b * 2.0 * s, blendScreen(b, 2.0 * s - 1.0), step(0.5, s));
|
|
2585
|
+
}
|
|
2586
|
+
vec3 blendSoftLight(vec3 b, vec3 s) {
|
|
2587
|
+
vec3 d = mix(sqrt(b), ((16.0 * b - 12.0) * b + 4.0) * b, step(b, vec3(0.25)));
|
|
2588
|
+
return mix(b - (1.0 - 2.0 * s) * b * (1.0 - b), b + (2.0 * s - 1.0) * (d - b), step(0.5, s));
|
|
2589
|
+
}
|
|
2590
|
+
vec3 blendColorDodge(vec3 b, vec3 s) {
|
|
2591
|
+
return min(vec3(1.0), b / max(1.0 - s, vec3(1e-5))) * step(1e-5, b);
|
|
2592
|
+
}
|
|
2593
|
+
vec3 blendOf(vec3 b, vec3 s) {
|
|
2594
|
+
if (u_blend < 0.5) return s;
|
|
2595
|
+
if (u_blend < 1.5) return b * s;
|
|
2596
|
+
if (u_blend < 2.5) return blendScreen(b, s);
|
|
2597
|
+
if (u_blend < 3.5) return blendHardLight(s, b);
|
|
2598
|
+
if (u_blend < 4.5) return blendSoftLight(b, s);
|
|
2599
|
+
return blendColorDodge(b, s);
|
|
2600
|
+
}
|
|
2601
|
+
`;
|
|
2602
|
+
/** Draw stage: the layer below and the blend-then-fade composite. The
|
|
2603
|
+
* blended colour is faded in by coverage x opacity in linear light, so an
|
|
2604
|
+
* antialiased edge keeps its true weight. */
|
|
2605
|
+
const COMPOSITE = `
|
|
2606
|
+
uniform float u_opacity;
|
|
2607
|
+
uniform sampler2D u_picture;
|
|
2608
|
+
vec3 pictureAt(vec2 fc) { return texture2D(u_picture, fc / u_resolution).rgb; }
|
|
2609
|
+
vec3 compositeLinear(vec3 b, vec3 s, float a) {
|
|
2610
|
+
return mix(toLinear(b), toLinear(blendOf(b, s)), clamp(a * u_opacity, 0.0, 1.0));
|
|
2611
|
+
}
|
|
2612
|
+
`;
|
|
2613
|
+
/** Mark effects: what the marks are drawn on. */
|
|
2614
|
+
const GROUND = `
|
|
2615
|
+
uniform float u_ground;
|
|
2616
|
+
vec3 backdropAt(vec2 fc) { return u_ground < 0.5 ? pictureAt(fc) : u_paper; }
|
|
2617
|
+
`;
|
|
2618
|
+
/** Filter effects (a whole replacement picture per cell) over the layer
|
|
2619
|
+
* below. Normal at full opacity hands the colour back untouched: no linear
|
|
2620
|
+
* round trip, so whole-pixel cells stay exact at 1080p and 4K. */
|
|
2621
|
+
const FILTER_COMPOSITE = `
|
|
2622
|
+
vec3 overPicture(vec3 s) {
|
|
2623
|
+
if (u_blend < 0.5 && u_opacity >= 1.0) return s;
|
|
2624
|
+
return toSrgb(compositeLinear(pictureAt(gl_FragCoord.xy), s, 1.0));
|
|
2625
|
+
}
|
|
2626
|
+
`;
|
|
2627
|
+
/**
|
|
2628
|
+
* Cell stage of a mark effect: the cell's tone becomes a mark strength in
|
|
2629
|
+
* 0..1. See SPEC-marks.md, Response.
|
|
2630
|
+
*
|
|
2631
|
+
* Direction: marks grow where they show. On an image ground a darkening
|
|
2632
|
+
* blend (multiply) grows them in the darks and every other blend in the
|
|
2633
|
+
* brights; on paper they grow away from the paper. Invert flips it.
|
|
2634
|
+
*
|
|
2635
|
+
* Motion multiplies the strength, so blank cells stay blank. Its angular
|
|
2636
|
+
* speed is snapped to whole cycles per loop and floored at one: it is the
|
|
2637
|
+
* effect's own motion, and freezing it on a short loop would read as broken.
|
|
2638
|
+
*
|
|
2639
|
+
* No shipped effect declares motion, motionSpeed or motionAmount yet (see
|
|
2640
|
+
* motionParams() below), so these uniforms are unset and read as 0: motion
|
|
2641
|
+
* is "none" and the strength above is unmodulated. The GLSL stays wired up
|
|
2642
|
+
* because motion ships later, once effects get a clock independent of the
|
|
2643
|
+
* source's own speed and pause; see SPEC-marks.md.
|
|
2644
|
+
*/
|
|
2645
|
+
const RESPONSE = `
|
|
2646
|
+
uniform float u_style;
|
|
2647
|
+
uniform float u_exposure;
|
|
2648
|
+
uniform float u_contrast;
|
|
2649
|
+
uniform float u_density;
|
|
2650
|
+
uniform float u_ground;
|
|
2651
|
+
uniform float u_blend;
|
|
2652
|
+
uniform float u_motion;
|
|
2653
|
+
uniform float u_motionSpeed;
|
|
2654
|
+
uniform float u_motionAmount;
|
|
2655
|
+
|
|
2656
|
+
bool growsInBrights() {
|
|
2657
|
+
if (u_ground > 0.5) return luma(u_paper) < 0.5;
|
|
2658
|
+
return abs(u_blend - 1.0) > 0.5;
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
float motionAt(vec2 cell) {
|
|
2662
|
+
if (u_motion < 0.5) return 0.0;
|
|
2663
|
+
float w = TAU * u_motionSpeed;
|
|
2664
|
+
if (u_loop > 0.0) w = TAU * max(1.0, floor(u_motionSpeed * u_loop + 0.5)) / u_loop;
|
|
2665
|
+
float ph = w * u_time;
|
|
2666
|
+
if (u_motion < 1.5) return sin(ph);
|
|
2667
|
+
if (u_motion < 2.5) {
|
|
2668
|
+
// 1.5 waves per frame height, travelling along a fixed diagonal.
|
|
2669
|
+
vec2 pos = cell * u_cellRef / u_refSize.y;
|
|
2670
|
+
return sin(ph - TAU * 1.5 * dot(pos, vec2(0.857, 0.514)));
|
|
2671
|
+
}
|
|
2672
|
+
return sin(ph + TAU * hash12(cell + 13.1));
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
float markStrength(vec3 c) {
|
|
2676
|
+
float tone = u_style < 0.5 ? luma(c) : 0.5;
|
|
2677
|
+
tone = clamp(tone * exp2(2.0 * u_exposure), 0.0, 1.0);
|
|
2678
|
+
float s = growsInBrights() ? tone : 1.0 - tone;
|
|
2679
|
+
if (u_invert > 0.5) s = 1.0 - s;
|
|
2680
|
+
s = clamp((s - 0.5) * u_contrast + 0.5, 0.0, 1.0);
|
|
2681
|
+
s = clamp(s * (1.0 + u_motionAmount * motionAt(cellIndex())), 0.0, 1.0);
|
|
2682
|
+
if (hash12(cellIndex() + vec2(71.3, 19.7)) >= u_density) s = 0.0;
|
|
2683
|
+
return s;
|
|
2684
|
+
}
|
|
2685
|
+
`;
|
|
2686
|
+
/** Append only: the uniform is the option's index. */
|
|
2687
|
+
const BLEND_OPTIONS = [
|
|
2688
|
+
{
|
|
2689
|
+
value: "normal",
|
|
2690
|
+
label: "Normal"
|
|
2691
|
+
},
|
|
2692
|
+
{
|
|
2693
|
+
value: "multiply",
|
|
2694
|
+
label: "Multiply"
|
|
2695
|
+
},
|
|
2696
|
+
{
|
|
2697
|
+
value: "screen",
|
|
2698
|
+
label: "Screen"
|
|
2699
|
+
},
|
|
2700
|
+
{
|
|
2701
|
+
value: "overlay",
|
|
2702
|
+
label: "Overlay"
|
|
2703
|
+
},
|
|
2704
|
+
{
|
|
2705
|
+
value: "softLight",
|
|
2706
|
+
label: "Soft light"
|
|
2707
|
+
},
|
|
2708
|
+
{
|
|
2709
|
+
value: "colorDodge",
|
|
2710
|
+
label: "Color dodge"
|
|
2711
|
+
}
|
|
2712
|
+
];
|
|
2713
|
+
function blendParams(d) {
|
|
2714
|
+
return [{
|
|
2715
|
+
key: "blend",
|
|
2716
|
+
label: "Blend",
|
|
2717
|
+
type: "enum",
|
|
2718
|
+
options: BLEND_OPTIONS,
|
|
2719
|
+
default: d.blend
|
|
2720
|
+
}, {
|
|
2721
|
+
key: "opacity",
|
|
2722
|
+
label: "Opacity",
|
|
2723
|
+
min: 0,
|
|
2724
|
+
max: 1,
|
|
2725
|
+
step: .01,
|
|
2726
|
+
default: d.opacity
|
|
2727
|
+
}];
|
|
2728
|
+
}
|
|
2729
|
+
function groundParams(d) {
|
|
2730
|
+
return [
|
|
2731
|
+
{
|
|
2732
|
+
key: "ground",
|
|
2733
|
+
label: "Ground",
|
|
2734
|
+
type: "enum",
|
|
2735
|
+
options: [{
|
|
2736
|
+
value: "image",
|
|
2737
|
+
label: "Image"
|
|
2738
|
+
}, {
|
|
2739
|
+
value: "paper",
|
|
2740
|
+
label: "Paper"
|
|
2741
|
+
}],
|
|
2742
|
+
default: d.ground
|
|
2743
|
+
},
|
|
2744
|
+
...blendParams(d),
|
|
2745
|
+
{
|
|
2746
|
+
key: "blur",
|
|
2747
|
+
label: "Blur",
|
|
2748
|
+
min: 0,
|
|
2749
|
+
max: 40,
|
|
2750
|
+
step: .5,
|
|
2751
|
+
default: 0,
|
|
2752
|
+
when: {
|
|
2753
|
+
key: "ground",
|
|
2754
|
+
in: ["image"]
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
];
|
|
2758
|
+
}
|
|
2759
|
+
function responseParams(d) {
|
|
2760
|
+
return [
|
|
2761
|
+
{
|
|
2762
|
+
key: "style",
|
|
2763
|
+
label: "Style",
|
|
2764
|
+
type: "enum",
|
|
2765
|
+
options: [{
|
|
2766
|
+
value: "filled",
|
|
2767
|
+
label: "Filled"
|
|
2768
|
+
}, {
|
|
2769
|
+
value: "uniform",
|
|
2770
|
+
label: "Uniform"
|
|
2771
|
+
}],
|
|
2772
|
+
default: "filled"
|
|
2773
|
+
},
|
|
2774
|
+
{
|
|
2775
|
+
key: "exposure",
|
|
2776
|
+
label: "Exposure",
|
|
2777
|
+
min: -1,
|
|
2778
|
+
max: 1,
|
|
2779
|
+
step: .01,
|
|
2780
|
+
default: 0
|
|
2781
|
+
},
|
|
2782
|
+
{
|
|
2783
|
+
key: "contrast",
|
|
2784
|
+
label: "Contrast",
|
|
2785
|
+
min: 0,
|
|
2786
|
+
max: 2,
|
|
2787
|
+
step: .01,
|
|
2788
|
+
default: d.contrast
|
|
2789
|
+
},
|
|
2790
|
+
{
|
|
2791
|
+
key: "density",
|
|
2792
|
+
label: "Density",
|
|
2793
|
+
min: 0,
|
|
2794
|
+
max: 1,
|
|
2795
|
+
step: .01,
|
|
2796
|
+
default: 1
|
|
2797
|
+
}
|
|
2798
|
+
];
|
|
2799
|
+
}
|
|
2800
|
+
/** grid.picture for mark effects. A paper ground never reads the picture,
|
|
2801
|
+
* so it gets none; blur only applies to an image ground. */
|
|
2802
|
+
function groundPicture(p) {
|
|
2803
|
+
return p.ground === "image" ? { blur: p.blur } : null;
|
|
2804
|
+
}
|
|
2805
|
+
/** grid.picture for filter effects (pixelate, dither). Normal at full
|
|
2806
|
+
* opacity replaces the picture outright (FILTER_COMPOSITE), so it is not
|
|
2807
|
+
* read and the stack skips rendering it. */
|
|
2808
|
+
function filterPicture(p) {
|
|
2809
|
+
return p.blend === "normal" && p.opacity >= 1 ? null : { blur: 0 };
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
//#endregion
|
|
2813
|
+
//#region src/effects/pixelate.ts
|
|
2814
|
+
const CELL_FRAGMENT$3 = `
|
|
2815
|
+
uniform float u_levels;
|
|
2816
|
+
|
|
2817
|
+
void main() {
|
|
2818
|
+
vec3 c = cellSource().rgb;
|
|
2819
|
+
// Posterize per channel. Rounds to the nearest level, so black and white
|
|
2820
|
+
// stay put. levels = 1 would divide by zero and means nothing; 0 is "off".
|
|
2821
|
+
if (u_levels >= 2.0) {
|
|
2822
|
+
float n = u_levels - 1.0;
|
|
2823
|
+
c = floor(c * n + 0.5) / n;
|
|
2824
|
+
}
|
|
2825
|
+
gl_FragColor = vec4(c, 1.0);
|
|
2826
|
+
}
|
|
2827
|
+
`;
|
|
2828
|
+
const DRAW_FRAGMENT$3 = `
|
|
2829
|
+
uniform float u_gap;
|
|
2830
|
+
uniform vec3 u_gapColor;
|
|
2831
|
+
${BLEND}
|
|
2832
|
+
${COMPOSITE}
|
|
2833
|
+
${FILTER_COMPOSITE}
|
|
2834
|
+
|
|
2835
|
+
void main() {
|
|
2836
|
+
vec3 color = coverage();
|
|
2837
|
+
if (u_gap > 0.0) {
|
|
2838
|
+
// A gap line is u_gap of a cell wide, centred on every cell boundary.
|
|
2839
|
+
// Its weight is the exact share of this output pixel it covers, and the
|
|
2840
|
+
// blend happens in linear light, for the same reason coverage() works
|
|
2841
|
+
// that way: a smoothstep edge in gamma space draws sub-pixel lines far
|
|
2842
|
+
// too heavy, so a small preview would look darker than its own export.
|
|
2843
|
+
vec2 px = cellPx();
|
|
2844
|
+
vec2 d = min(cellLocal(), 1.0 - cellLocal()) * px; // px to nearest boundary
|
|
2845
|
+
vec2 hw = 0.5 * u_gap * px; // line half-width in px
|
|
2846
|
+
vec2 cov = clamp(min(hw, d + 0.5) - max(-hw, d - 0.5), 0.0, 1.0);
|
|
2847
|
+
float m = 1.0 - (1.0 - cov.x) * (1.0 - cov.y);
|
|
2848
|
+
color = toSrgb(mix(toLinear(color), toLinear(u_gapColor), m));
|
|
2849
|
+
}
|
|
2850
|
+
gl_FragColor = vec4(overPicture(color), 1.0);
|
|
2851
|
+
}
|
|
2852
|
+
`;
|
|
2853
|
+
const pixelate = {
|
|
2854
|
+
id: "pixelate",
|
|
2855
|
+
label: "Pixelate",
|
|
2856
|
+
fragment: DRAW_FRAGMENT$3,
|
|
2857
|
+
grid: {
|
|
2858
|
+
cell: (p) => [p.size, p.size],
|
|
2859
|
+
fragment: CELL_FRAGMENT$3,
|
|
2860
|
+
picture: filterPicture
|
|
2861
|
+
},
|
|
2862
|
+
params: [
|
|
2863
|
+
{
|
|
2864
|
+
key: "size",
|
|
2865
|
+
label: "Size",
|
|
2866
|
+
min: 2,
|
|
2867
|
+
max: 160,
|
|
2868
|
+
step: 1,
|
|
2869
|
+
default: 13
|
|
2870
|
+
},
|
|
2871
|
+
{
|
|
2872
|
+
key: "levels",
|
|
2873
|
+
label: "Levels",
|
|
2874
|
+
min: 0,
|
|
2875
|
+
max: 16,
|
|
2876
|
+
step: 1,
|
|
2877
|
+
default: 0
|
|
2878
|
+
},
|
|
2879
|
+
{
|
|
2880
|
+
key: "gap",
|
|
2881
|
+
label: "Gap",
|
|
2882
|
+
min: 0,
|
|
2883
|
+
max: .4,
|
|
2884
|
+
step: .01,
|
|
2885
|
+
default: .08
|
|
2886
|
+
},
|
|
2887
|
+
{
|
|
2888
|
+
key: "gapColor",
|
|
2889
|
+
label: "Gap color",
|
|
2890
|
+
type: "color",
|
|
2891
|
+
default: "#000000"
|
|
2892
|
+
},
|
|
2893
|
+
...blendParams({
|
|
2894
|
+
blend: "normal",
|
|
2895
|
+
opacity: 1
|
|
2896
|
+
})
|
|
2897
|
+
],
|
|
2898
|
+
randomParams(rand) {
|
|
2899
|
+
return {
|
|
2900
|
+
size: Math.round(10 + rand() * 50),
|
|
2901
|
+
levels: rand() < .5 ? 0 : Math.round(3 + rand() * 5),
|
|
2902
|
+
gap: rand() < .6 ? 0 : .05 + rand() * .15,
|
|
2903
|
+
gapColor: "#000000",
|
|
2904
|
+
blend: "normal",
|
|
2905
|
+
opacity: 1
|
|
2906
|
+
};
|
|
2907
|
+
}
|
|
2908
|
+
};
|
|
2663
2909
|
|
|
2664
2910
|
//#endregion
|
|
2665
2911
|
//#region src/effects/blueNoise.ts
|
|
@@ -2750,12 +2996,23 @@ void main() {
|
|
|
2750
2996
|
gl_FragColor = vec4(color, 1.0);
|
|
2751
2997
|
}
|
|
2752
2998
|
`;
|
|
2999
|
+
/** Coverage upscale, then over the picture (FILTER_COMPOSITE). */
|
|
3000
|
+
const DRAW_FRAGMENT$2 = `
|
|
3001
|
+
${BLEND}
|
|
3002
|
+
${COMPOSITE}
|
|
3003
|
+
${FILTER_COMPOSITE}
|
|
3004
|
+
void main() {
|
|
3005
|
+
gl_FragColor = vec4(overPicture(coverage()), 1.0);
|
|
3006
|
+
}
|
|
3007
|
+
`;
|
|
2753
3008
|
const dither = {
|
|
2754
3009
|
id: "dither",
|
|
2755
3010
|
label: "Dither",
|
|
3011
|
+
fragment: DRAW_FRAGMENT$2,
|
|
2756
3012
|
grid: {
|
|
2757
3013
|
cell: (p) => [p.size, p.size],
|
|
2758
|
-
fragment: CELL_FRAGMENT$2
|
|
3014
|
+
fragment: CELL_FRAGMENT$2,
|
|
3015
|
+
picture: filterPicture
|
|
2759
3016
|
},
|
|
2760
3017
|
textures: [{
|
|
2761
3018
|
uniform: "u_noise",
|
|
@@ -2833,7 +3090,15 @@ const dither = {
|
|
|
2833
3090
|
step: 1,
|
|
2834
3091
|
default: 0
|
|
2835
3092
|
},
|
|
2836
|
-
...colorModeParams({
|
|
3093
|
+
...colorModeParams({
|
|
3094
|
+
mode: "duotone",
|
|
3095
|
+
ink: "#000000",
|
|
3096
|
+
paper: "#ffffff"
|
|
3097
|
+
}),
|
|
3098
|
+
...blendParams({
|
|
3099
|
+
blend: "screen",
|
|
3100
|
+
opacity: .6
|
|
3101
|
+
})
|
|
2837
3102
|
],
|
|
2838
3103
|
randomParams(rand) {
|
|
2839
3104
|
const patterns = [
|
|
@@ -2847,7 +3112,13 @@ const dither = {
|
|
|
2847
3112
|
"duotone",
|
|
2848
3113
|
"palette"
|
|
2849
3114
|
];
|
|
2850
|
-
|
|
3115
|
+
const blends = [
|
|
3116
|
+
"normal",
|
|
3117
|
+
"screen",
|
|
3118
|
+
"overlay",
|
|
3119
|
+
"softLight"
|
|
3120
|
+
];
|
|
3121
|
+
const p = {
|
|
2851
3122
|
pattern: patterns[Math.floor(rand() * patterns.length)],
|
|
2852
3123
|
size: Math.round(2 + rand() * 6),
|
|
2853
3124
|
levels: Math.round(2 + rand() * 3),
|
|
@@ -2857,8 +3128,15 @@ const dither = {
|
|
|
2857
3128
|
colorMode: modes[Math.floor(rand() * modes.length)],
|
|
2858
3129
|
ink: "#111111",
|
|
2859
3130
|
paper: "#f4f1ea",
|
|
2860
|
-
invert: false
|
|
3131
|
+
invert: false,
|
|
3132
|
+
blend: blends[Math.floor(rand() * blends.length)],
|
|
3133
|
+
opacity: .6 + rand() * .4
|
|
2861
3134
|
};
|
|
3135
|
+
if (p.blend === "screen" || p.blend === "colorDodge") {
|
|
3136
|
+
p.ink = "#000000";
|
|
3137
|
+
p.paper = "#ffffff";
|
|
3138
|
+
}
|
|
3139
|
+
return p;
|
|
2862
3140
|
}
|
|
2863
3141
|
};
|
|
2864
3142
|
|
|
@@ -2866,26 +3144,27 @@ const dither = {
|
|
|
2866
3144
|
//#region src/effects/halftone.ts
|
|
2867
3145
|
const ROW = .8660254;
|
|
2868
3146
|
const CELL_FRAGMENT$1 = `
|
|
2869
|
-
uniform float u_contrast;
|
|
2870
3147
|
${COLOR_MODE}
|
|
3148
|
+
${RESPONSE}
|
|
2871
3149
|
|
|
2872
3150
|
void main() {
|
|
2873
3151
|
vec3 sc = cellSource().rgb;
|
|
3152
|
+
float ink = markStrength(sc);
|
|
2874
3153
|
|
|
2875
|
-
// Ink
|
|
2876
|
-
//
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
float ink = clamp((1.0 - tone - 0.5) * u_contrast + 0.5, 0.0, 1.0);
|
|
2880
|
-
|
|
2881
|
-
// Ink color. Duotone inks every shape with u_ink (colorize() would mix
|
|
2882
|
-
// toward paper by tone, which is dither's meaning of duotone). Palette
|
|
2883
|
-
// inks each dot with the nearest palette STOP to the un-inverted tone, so
|
|
2884
|
-
// Invert flips sizes and leaves colors, and the dots are flat palette
|
|
2885
|
-
// colors rather than a copy of the source.
|
|
3154
|
+
// Ink color. Duotone inks every shape with u_ink. Palette inks each dot
|
|
3155
|
+
// with the nearest palette STOP to the un-inverted tone, so Invert flips
|
|
3156
|
+
// sizes and leaves colors, and the dots are flat palette colors rather
|
|
3157
|
+
// than a copy of the source.
|
|
2886
3158
|
vec3 color = sc;
|
|
2887
3159
|
if (u_colorMode > 1.5) color = paletteStop(toneIsRamp() ? rampPosition(sc) : luma(sc));
|
|
2888
3160
|
else if (u_colorMode > 0.5) color = u_ink;
|
|
3161
|
+
else if (u_ground < 0.5) {
|
|
3162
|
+
// Over the picture, a shape in the picture's own colour blends into
|
|
3163
|
+
// itself and vanishes. Lift it most of the way to full brightness, as
|
|
3164
|
+
// ASCII does, so it reads as a vivid version of what is under it.
|
|
3165
|
+
float peak = max(max(sc.r, sc.g), max(sc.b, 1e-3));
|
|
3166
|
+
color = mix(sc, sc / peak, 0.75);
|
|
3167
|
+
}
|
|
2889
3168
|
|
|
2890
3169
|
gl_FragColor = vec4(color, ink);
|
|
2891
3170
|
}
|
|
@@ -2895,8 +3174,10 @@ uniform float u_grid;
|
|
|
2895
3174
|
uniform float u_shape;
|
|
2896
3175
|
uniform float u_radius;
|
|
2897
3176
|
uniform float u_softness;
|
|
2898
|
-
uniform float u_pulse;
|
|
2899
3177
|
${COLOR_MODE}
|
|
3178
|
+
${BLEND}
|
|
3179
|
+
${COMPOSITE}
|
|
3180
|
+
${GROUND}
|
|
2900
3181
|
|
|
2901
3182
|
const float ROW = ${ROW};
|
|
2902
3183
|
|
|
@@ -2905,9 +3186,10 @@ vec4 lattice(vec2 k) {
|
|
|
2905
3186
|
return texture2D(u_cells, (kk + 0.5) / u_gridSize);
|
|
2906
3187
|
}
|
|
2907
3188
|
|
|
2908
|
-
// The shape covering lattice coordinate g most,
|
|
2909
|
-
// linear light. \`aa\` is the width, in lattice units,
|
|
2910
|
-
|
|
3189
|
+
// The shape covering lattice coordinate g most, composited onto backdrop b
|
|
3190
|
+
// (sRGB), returned in linear light. \`aa\` is the width, in lattice units,
|
|
3191
|
+
// edges are ramped over.
|
|
3192
|
+
vec3 shade(vec2 g, float aa, vec3 b) {
|
|
2911
3193
|
bool hex = u_grid > 0.5;
|
|
2912
3194
|
bool line = u_shape > 0.5 && u_shape < 1.5;
|
|
2913
3195
|
// Texel units -> lattice units (1 = the pitch the user set as Size).
|
|
@@ -2930,8 +3212,7 @@ vec3 shade(vec2 g, float aa, float w) {
|
|
|
2930
3212
|
float kl = stride * floor((g.x - 0.5 - par) / stride) + par;
|
|
2931
3213
|
float f = (g.x - 0.5 - kl) / stride;
|
|
2932
3214
|
vec4 v = mix(lattice(vec2(kl, j)), lattice(vec2(kl + stride, j)), f);
|
|
2933
|
-
float
|
|
2934
|
-
float extent = min(0.5 * v.a * unit.y * u_radius * swell, 1.0);
|
|
3215
|
+
float extent = min(0.5 * v.a * unit.y * u_radius, 1.0);
|
|
2935
3216
|
float ramp = aa + u_softness * extent;
|
|
2936
3217
|
float mask = extent <= 0.0 ? 0.0 : clamp((extent - abs(dy)) / ramp + 0.5, 0.0, 1.0);
|
|
2937
3218
|
if (mask > bestMask) { bestMask = mask; bestInk = v.rgb; }
|
|
@@ -2943,20 +3224,23 @@ vec3 shade(vec2 g, float aa, float w) {
|
|
|
2943
3224
|
vec2 k = vec2(k0 + stride * float(di), j);
|
|
2944
3225
|
vec4 v = lattice(k);
|
|
2945
3226
|
vec2 d = vec2((g.x - (k.x + 0.5)) * unit.x, dy);
|
|
2946
|
-
vec2 id = k - 0.5 * u_gridSize;
|
|
2947
|
-
float swell = 1.0 + 0.25 * u_pulse * sin(w * u_time + dot(id * unit, vec2(0.35, 0.22)) + u_seed);
|
|
2948
|
-
|
|
2949
3227
|
float dist;
|
|
2950
3228
|
float extent;
|
|
3229
|
+
// On paper, shape AREA follows ink: the print rule that reproduces
|
|
3230
|
+
// tone. Over the picture there is no tone to reproduce, and area
|
|
3231
|
+
// sizing squeezes the visible size range (half the ink is still 71%
|
|
3232
|
+
// of the full width), so there the width follows strength directly:
|
|
3233
|
+
// empty cells stay empty and full ones fill.
|
|
3234
|
+
float grow = u_ground < 0.5 ? v.a : sqrt(v.a);
|
|
2951
3235
|
if (u_shape < 0.5) {
|
|
2952
3236
|
// Dot: area pi * rho^2 out of a cell of area 1 (square) or ROW (hex).
|
|
2953
3237
|
dist = length(d);
|
|
2954
|
-
extent =
|
|
3238
|
+
extent = grow * sqrt((hex ? ROW : 1.0) / 3.14159265);
|
|
2955
3239
|
} else {
|
|
2956
3240
|
dist = max(abs(d.x), abs(d.y));
|
|
2957
|
-
extent = 0.5 *
|
|
3241
|
+
extent = 0.5 * grow;
|
|
2958
3242
|
}
|
|
2959
|
-
extent = min(extent * u_radius
|
|
3243
|
+
extent = min(extent * u_radius, 1.0);
|
|
2960
3244
|
|
|
2961
3245
|
// Coverage of a straight edge by a box is linear in the distance to
|
|
2962
3246
|
// it. Softness widens that ramp into a blur.
|
|
@@ -2965,22 +3249,12 @@ vec3 shade(vec2 g, float aa, float w) {
|
|
|
2965
3249
|
if (mask > bestMask) { bestMask = mask; bestInk = v.rgb; }
|
|
2966
3250
|
}
|
|
2967
3251
|
}
|
|
2968
|
-
return
|
|
3252
|
+
return compositeLinear(b, bestInk, bestMask);
|
|
2969
3253
|
}
|
|
2970
3254
|
|
|
2971
3255
|
void main() {
|
|
2972
3256
|
float pitchRef = u_grid > 0.5 ? u_cellRef.x * 2.0 : u_cellRef.x;
|
|
2973
|
-
// One output pixel, in lattice units.
|
|
2974
3257
|
float px = 1.0 / (pitchRef * u_outputScale);
|
|
2975
|
-
|
|
2976
|
-
// Pulse: every shape swells and shrinks, phase-shifted across the lattice
|
|
2977
|
-
// so the swell travels through the frame instead of throbbing in lockstep.
|
|
2978
|
-
// Snapped to whole cycles per loop and floored at one: unlike a generator's
|
|
2979
|
-
// slow secondary swell, this is the effect's only motion, so freezing it at
|
|
2980
|
-
// short loops (loopFreq's choice) would read as the knob being broken.
|
|
2981
|
-
float w = 0.8;
|
|
2982
|
-
if (u_loop > 0.0) w = TAU * max(1.0, floor(w * u_loop / TAU + 0.5)) / u_loop;
|
|
2983
|
-
|
|
2984
3258
|
// Supersampled: n x n samples per pixel, each ramping its edges over 1/n
|
|
2985
3259
|
// of a pixel. One sample with a one-pixel linear ramp is exact only for a
|
|
2986
3260
|
// straight edge parallel to the pixel grid; on a 5px preview dot every
|
|
@@ -2995,8 +3269,8 @@ void main() {
|
|
|
2995
3269
|
for (int sy = 0; sy < 4; sy++) {
|
|
2996
3270
|
for (int sx = 0; sx < 4; sx++) {
|
|
2997
3271
|
if (float(sx) >= n || float(sy) >= n) continue;
|
|
2998
|
-
vec2
|
|
2999
|
-
acc += shade(cellCoordAt(
|
|
3272
|
+
vec2 fc = gl_FragCoord.xy + (vec2(float(sx), float(sy)) + 0.5) / n - 0.5;
|
|
3273
|
+
acc += shade(cellCoordAt(fc), px / n, backdropAt(fc));
|
|
3000
3274
|
}
|
|
3001
3275
|
}
|
|
3002
3276
|
gl_FragColor = vec4(toSrgb(acc / (n * n)), 1.0);
|
|
@@ -3013,9 +3287,15 @@ const halftone = {
|
|
|
3013
3287
|
grid: {
|
|
3014
3288
|
cell: cellOf$1,
|
|
3015
3289
|
lattice: (p) => ({ angle: p.angle }),
|
|
3290
|
+
picture: groundPicture,
|
|
3016
3291
|
fragment: CELL_FRAGMENT$1
|
|
3017
3292
|
},
|
|
3018
3293
|
params: [
|
|
3294
|
+
...groundParams({
|
|
3295
|
+
ground: "image",
|
|
3296
|
+
blend: "screen",
|
|
3297
|
+
opacity: 1
|
|
3298
|
+
}),
|
|
3019
3299
|
{
|
|
3020
3300
|
key: "grid",
|
|
3021
3301
|
label: "Grid",
|
|
@@ -3047,7 +3327,7 @@ const halftone = {
|
|
|
3047
3327
|
label: "Square"
|
|
3048
3328
|
}
|
|
3049
3329
|
],
|
|
3050
|
-
default: "
|
|
3330
|
+
default: "square"
|
|
3051
3331
|
},
|
|
3052
3332
|
{
|
|
3053
3333
|
key: "size",
|
|
@@ -3055,7 +3335,7 @@ const halftone = {
|
|
|
3055
3335
|
min: 6,
|
|
3056
3336
|
max: 160,
|
|
3057
3337
|
step: .5,
|
|
3058
|
-
default:
|
|
3338
|
+
default: 20
|
|
3059
3339
|
},
|
|
3060
3340
|
{
|
|
3061
3341
|
key: "angle",
|
|
@@ -3071,7 +3351,7 @@ const halftone = {
|
|
|
3071
3351
|
min: .2,
|
|
3072
3352
|
max: 1.5,
|
|
3073
3353
|
step: .01,
|
|
3074
|
-
default:
|
|
3354
|
+
default: .75
|
|
3075
3355
|
},
|
|
3076
3356
|
{
|
|
3077
3357
|
key: "softness",
|
|
@@ -3081,25 +3361,14 @@ const halftone = {
|
|
|
3081
3361
|
step: .01,
|
|
3082
3362
|
default: .1
|
|
3083
3363
|
},
|
|
3084
|
-
{
|
|
3085
|
-
key: "contrast",
|
|
3086
|
-
label: "Contrast",
|
|
3087
|
-
min: 0,
|
|
3088
|
-
max: 2,
|
|
3089
|
-
step: .01,
|
|
3090
|
-
default: 1.15
|
|
3091
|
-
},
|
|
3092
|
-
{
|
|
3093
|
-
key: "pulse",
|
|
3094
|
-
label: "Pulse",
|
|
3095
|
-
min: 0,
|
|
3096
|
-
max: 1,
|
|
3097
|
-
step: .01,
|
|
3098
|
-
default: 0
|
|
3099
|
-
},
|
|
3364
|
+
...responseParams({ contrast: 1.15 }),
|
|
3100
3365
|
...colorModeParams({
|
|
3101
|
-
mode: "
|
|
3102
|
-
|
|
3366
|
+
mode: "source",
|
|
3367
|
+
paperWhen: {
|
|
3368
|
+
key: "ground",
|
|
3369
|
+
in: ["paper"]
|
|
3370
|
+
},
|
|
3371
|
+
singleColour: true
|
|
3103
3372
|
})
|
|
3104
3373
|
],
|
|
3105
3374
|
randomParams(rand) {
|
|
@@ -3114,17 +3383,34 @@ const halftone = {
|
|
|
3114
3383
|
"duotone",
|
|
3115
3384
|
"palette"
|
|
3116
3385
|
];
|
|
3386
|
+
const ground = rand() < .8 ? "image" : "paper";
|
|
3387
|
+
const blends = ground === "paper" ? ["normal", "multiply"] : [
|
|
3388
|
+
"normal",
|
|
3389
|
+
"multiply",
|
|
3390
|
+
"screen",
|
|
3391
|
+
"screen",
|
|
3392
|
+
"overlay",
|
|
3393
|
+
"softLight",
|
|
3394
|
+
"colorDodge"
|
|
3395
|
+
];
|
|
3396
|
+
const blend = blends[Math.floor(rand() * blends.length)];
|
|
3117
3397
|
return {
|
|
3398
|
+
ground,
|
|
3399
|
+
blend,
|
|
3400
|
+
opacity: .6 + rand() * .4,
|
|
3401
|
+
blur: 0,
|
|
3118
3402
|
grid: rand() < .5 ? "square" : "hex",
|
|
3119
3403
|
shape: shapes[Math.floor(rand() * shapes.length)],
|
|
3120
3404
|
size: Math.round(8 + rand() * 30),
|
|
3121
3405
|
angle: Math.round(rand() * 180),
|
|
3122
|
-
radius:
|
|
3406
|
+
radius: .7 + rand() * .7,
|
|
3123
3407
|
softness: rand() < .7 ? .1 : rand() * .5,
|
|
3408
|
+
style: "filled",
|
|
3409
|
+
exposure: 0,
|
|
3124
3410
|
contrast: 1.15,
|
|
3125
|
-
|
|
3411
|
+
density: 1,
|
|
3126
3412
|
colorMode: modes[Math.floor(rand() * modes.length)],
|
|
3127
|
-
ink: "#111111",
|
|
3413
|
+
ink: blend === "screen" || blend === "colorDodge" ? "#ffffff" : "#111111",
|
|
3128
3414
|
paper: "#f4f1ea",
|
|
3129
3415
|
invert: false
|
|
3130
3416
|
};
|
|
@@ -3362,11 +3648,13 @@ uniform float u_cycleAmount;
|
|
|
3362
3648
|
uniform float u_glyphCount;
|
|
3363
3649
|
uniform sampler2D u_lut;
|
|
3364
3650
|
${COLOR_MODE}
|
|
3651
|
+
${RESPONSE}
|
|
3365
3652
|
|
|
3366
3653
|
void main() {
|
|
3367
3654
|
vec3 sc = cellSource().rgb;
|
|
3368
|
-
|
|
3369
|
-
|
|
3655
|
+
// Glyph weight is the shared mark strength: a stronger mark is a denser
|
|
3656
|
+
// glyph, and a cell the density knob empties gets the blank glyph.
|
|
3657
|
+
float tone = markStrength(sc);
|
|
3370
3658
|
|
|
3371
3659
|
// The lookup places glyphs by measured ink coverage: R = the glyph just
|
|
3372
3660
|
// below this tone, G = the one just above, B = how far between them.
|
|
@@ -3380,13 +3668,22 @@ void main() {
|
|
|
3380
3668
|
float t = u_smooth > 0.5 ? 0.02 + 0.96 * hash12(cellIndex() + 0.5) : 0.5;
|
|
3381
3669
|
float glyph = l.b > t ? hi : lo;
|
|
3382
3670
|
|
|
3383
|
-
// Cycle: every step
|
|
3384
|
-
// similar weight, so the picture flickers but stays
|
|
3671
|
+
// Cycle: every step a share of the cells re-rolls its glyph among
|
|
3672
|
+
// neighbours of similar weight, so the picture flickers but stays
|
|
3673
|
+
// readable. cycleAmount is that share, and also how far a cell may move
|
|
3674
|
+
// (one glyph at small amounts, up to a quarter of the set at 1): a small
|
|
3675
|
+
// amount must flicker a few cells, not round every move down to nothing,
|
|
3676
|
+
// which is what a pure distance scale did on a 10-glyph set. Blank cells
|
|
3385
3677
|
// stay blank, or dark areas would fill with sparks.
|
|
3386
3678
|
if (u_cycle > 0.0 && glyph > 0.5) {
|
|
3387
|
-
float
|
|
3388
|
-
float
|
|
3389
|
-
|
|
3679
|
+
float st = loopStep(u_cycle);
|
|
3680
|
+
float pick = hash12(cellIndex() + vec2(11.0, 53.0) * st + u_seed + 3.1);
|
|
3681
|
+
if (pick < u_cycleAmount) {
|
|
3682
|
+
float h = hash12(cellIndex() + vec2(37.0, 17.0) * st + u_seed);
|
|
3683
|
+
float reach = max(1.0, floor(u_cycleAmount * u_glyphCount * 0.25));
|
|
3684
|
+
float move = (h < 0.5 ? -1.0 : 1.0) * (1.0 + floor(fract(h * 2.0) * reach));
|
|
3685
|
+
glyph = clamp(glyph + move, 1.0, u_glyphCount - 1.0);
|
|
3686
|
+
}
|
|
3390
3687
|
}
|
|
3391
3688
|
|
|
3392
3689
|
// Glyph color. Tone already lives in the glyph's density, so source colors
|
|
@@ -3412,6 +3709,9 @@ uniform vec2 u_atlasGrid; // glyph columns, rows
|
|
|
3412
3709
|
uniform vec2 u_atlasSize; // atlas size in texels
|
|
3413
3710
|
uniform vec2 u_glyphPx; // one glyph's size in atlas texels (without gutter)
|
|
3414
3711
|
${COLOR_MODE}
|
|
3712
|
+
${BLEND}
|
|
3713
|
+
${COMPOSITE}
|
|
3714
|
+
${GROUND}
|
|
3415
3715
|
|
|
3416
3716
|
void main() {
|
|
3417
3717
|
vec4 cell = cellValue(cellIndexAt());
|
|
@@ -3440,8 +3740,7 @@ void main() {
|
|
|
3440
3740
|
}
|
|
3441
3741
|
a /= n * n;
|
|
3442
3742
|
|
|
3443
|
-
|
|
3444
|
-
gl_FragColor = vec4(color, 1.0);
|
|
3743
|
+
gl_FragColor = vec4(toSrgb(compositeLinear(backdropAt(gl_FragCoord.xy), cell.rgb, a)), 1.0);
|
|
3445
3744
|
}
|
|
3446
3745
|
`;
|
|
3447
3746
|
function cellOf(p) {
|
|
@@ -3457,7 +3756,8 @@ const ascii = {
|
|
|
3457
3756
|
fragment: DRAW_FRAGMENT,
|
|
3458
3757
|
grid: {
|
|
3459
3758
|
cell: cellOf,
|
|
3460
|
-
fragment: CELL_FRAGMENT
|
|
3759
|
+
fragment: CELL_FRAGMENT,
|
|
3760
|
+
picture: groundPicture
|
|
3461
3761
|
},
|
|
3462
3762
|
textures: [{
|
|
3463
3763
|
uniform: "u_atlas",
|
|
@@ -3477,6 +3777,11 @@ const ascii = {
|
|
|
3477
3777
|
})
|
|
3478
3778
|
}],
|
|
3479
3779
|
params: [
|
|
3780
|
+
...groundParams({
|
|
3781
|
+
ground: "image",
|
|
3782
|
+
blend: "colorDodge",
|
|
3783
|
+
opacity: 1
|
|
3784
|
+
}),
|
|
3480
3785
|
{
|
|
3481
3786
|
key: "charset",
|
|
3482
3787
|
label: "Characters",
|
|
@@ -3515,7 +3820,7 @@ const ascii = {
|
|
|
3515
3820
|
min: 8,
|
|
3516
3821
|
max: 96,
|
|
3517
3822
|
step: 1,
|
|
3518
|
-
default:
|
|
3823
|
+
default: 10
|
|
3519
3824
|
},
|
|
3520
3825
|
{
|
|
3521
3826
|
key: "smooth",
|
|
@@ -3529,7 +3834,7 @@ const ascii = {
|
|
|
3529
3834
|
min: 0,
|
|
3530
3835
|
max: 12,
|
|
3531
3836
|
step: 1,
|
|
3532
|
-
default:
|
|
3837
|
+
default: 2
|
|
3533
3838
|
},
|
|
3534
3839
|
{
|
|
3535
3840
|
key: "cycleAmount",
|
|
@@ -3537,13 +3842,18 @@ const ascii = {
|
|
|
3537
3842
|
min: 0,
|
|
3538
3843
|
max: 1,
|
|
3539
3844
|
step: .01,
|
|
3540
|
-
default: .
|
|
3845
|
+
default: .2
|
|
3541
3846
|
},
|
|
3847
|
+
...responseParams({ contrast: 1.2 }),
|
|
3542
3848
|
...colorModeParams({
|
|
3543
3849
|
mode: "source",
|
|
3544
3850
|
ink: "#e8ffe8",
|
|
3545
3851
|
paper: "#000000",
|
|
3546
|
-
|
|
3852
|
+
paperWhen: {
|
|
3853
|
+
key: "ground",
|
|
3854
|
+
in: ["paper"]
|
|
3855
|
+
},
|
|
3856
|
+
singleColour: true
|
|
3547
3857
|
})
|
|
3548
3858
|
],
|
|
3549
3859
|
randomParams(rand) {
|
|
@@ -3559,12 +3869,28 @@ const ascii = {
|
|
|
3559
3869
|
"duotone",
|
|
3560
3870
|
"palette"
|
|
3561
3871
|
];
|
|
3872
|
+
const ground = rand() < .8 ? "image" : "paper";
|
|
3873
|
+
const blends = ground === "paper" ? ["normal"] : [
|
|
3874
|
+
"normal",
|
|
3875
|
+
"screen",
|
|
3876
|
+
"screen",
|
|
3877
|
+
"overlay",
|
|
3878
|
+
"colorDodge"
|
|
3879
|
+
];
|
|
3562
3880
|
return {
|
|
3881
|
+
ground,
|
|
3882
|
+
blend: blends[Math.floor(rand() * blends.length)],
|
|
3883
|
+
opacity: .7 + rand() * .3,
|
|
3884
|
+
blur: 0,
|
|
3563
3885
|
charset: sets[Math.floor(rand() * sets.length)],
|
|
3564
3886
|
size: Math.round(14 + rand() * 30),
|
|
3565
3887
|
smooth: true,
|
|
3566
|
-
cycle:
|
|
3567
|
-
cycleAmount: .
|
|
3888
|
+
cycle: 2,
|
|
3889
|
+
cycleAmount: .2,
|
|
3890
|
+
style: "filled",
|
|
3891
|
+
exposure: 0,
|
|
3892
|
+
contrast: 1,
|
|
3893
|
+
density: 1,
|
|
3568
3894
|
colorMode: modes[Math.floor(rand() * modes.length)],
|
|
3569
3895
|
ink: "#e8ffe8",
|
|
3570
3896
|
paper: "#000000",
|
|
@@ -3573,6 +3899,77 @@ const ascii = {
|
|
|
3573
3899
|
}
|
|
3574
3900
|
};
|
|
3575
3901
|
|
|
3902
|
+
//#endregion
|
|
3903
|
+
//#region src/effects/tint.ts
|
|
3904
|
+
const FRAGMENT = `
|
|
3905
|
+
uniform float u_mode;
|
|
3906
|
+
uniform vec3 u_dark;
|
|
3907
|
+
uniform vec3 u_light;
|
|
3908
|
+
uniform float u_amount;
|
|
3909
|
+
void main() {
|
|
3910
|
+
vec3 c = source(v_uv).rgb;
|
|
3911
|
+
float t = luma(c);
|
|
3912
|
+
vec3 mapped = u_mode < 0.5 ? mix(u_dark, u_light, t) : palette(t);
|
|
3913
|
+
gl_FragColor = vec4(toSrgb(mix(toLinear(c), toLinear(mapped), u_amount)), 1.0);
|
|
3914
|
+
}
|
|
3915
|
+
`;
|
|
3916
|
+
const tint = {
|
|
3917
|
+
id: "tint",
|
|
3918
|
+
label: "Tint",
|
|
3919
|
+
fragment: FRAGMENT,
|
|
3920
|
+
params: [
|
|
3921
|
+
{
|
|
3922
|
+
key: "mode",
|
|
3923
|
+
label: "Mode",
|
|
3924
|
+
type: "enum",
|
|
3925
|
+
options: [{
|
|
3926
|
+
value: "duotone",
|
|
3927
|
+
label: "Duotone"
|
|
3928
|
+
}, {
|
|
3929
|
+
value: "palette",
|
|
3930
|
+
label: "Palette"
|
|
3931
|
+
}],
|
|
3932
|
+
default: "duotone"
|
|
3933
|
+
},
|
|
3934
|
+
{
|
|
3935
|
+
key: "dark",
|
|
3936
|
+
label: "Dark",
|
|
3937
|
+
type: "color",
|
|
3938
|
+
default: "#1b1a4a",
|
|
3939
|
+
when: {
|
|
3940
|
+
key: "mode",
|
|
3941
|
+
in: ["duotone"]
|
|
3942
|
+
}
|
|
3943
|
+
},
|
|
3944
|
+
{
|
|
3945
|
+
key: "light",
|
|
3946
|
+
label: "Light",
|
|
3947
|
+
type: "color",
|
|
3948
|
+
default: "#f2c6a0",
|
|
3949
|
+
when: {
|
|
3950
|
+
key: "mode",
|
|
3951
|
+
in: ["duotone"]
|
|
3952
|
+
}
|
|
3953
|
+
},
|
|
3954
|
+
{
|
|
3955
|
+
key: "amount",
|
|
3956
|
+
label: "Amount",
|
|
3957
|
+
min: 0,
|
|
3958
|
+
max: 1,
|
|
3959
|
+
step: .01,
|
|
3960
|
+
default: 1
|
|
3961
|
+
}
|
|
3962
|
+
],
|
|
3963
|
+
randomParams(rand) {
|
|
3964
|
+
return {
|
|
3965
|
+
mode: rand() < .5 ? "duotone" : "palette",
|
|
3966
|
+
dark: "#1b1a4a",
|
|
3967
|
+
light: "#f2c6a0",
|
|
3968
|
+
amount: .6 + rand() * .4
|
|
3969
|
+
};
|
|
3970
|
+
}
|
|
3971
|
+
};
|
|
3972
|
+
|
|
3576
3973
|
//#endregion
|
|
3577
3974
|
//#region src/registry.ts
|
|
3578
3975
|
const shaders = [
|
|
@@ -3598,7 +3995,8 @@ const effects = [
|
|
|
3598
3995
|
pixelate,
|
|
3599
3996
|
dither,
|
|
3600
3997
|
halftone,
|
|
3601
|
-
ascii
|
|
3998
|
+
ascii,
|
|
3999
|
+
tint
|
|
3602
4000
|
];
|
|
3603
4001
|
function getEffect(id) {
|
|
3604
4002
|
return effects.find((e) => e.id === id);
|
|
@@ -4196,6 +4594,41 @@ function resolveParams(def, overrides) {
|
|
|
4196
4594
|
|
|
4197
4595
|
//#endregion
|
|
4198
4596
|
//#region src/stack.ts
|
|
4597
|
+
/** Backdrop blur, pass 1: box-average the frame down by k (4x4 LINEAR taps
|
|
4598
|
+
* spread over one small texel). */
|
|
4599
|
+
const BLUR_DOWN_FRAGMENT = `
|
|
4600
|
+
uniform sampler2D u_source;
|
|
4601
|
+
uniform vec2 u_span;
|
|
4602
|
+
void main() {
|
|
4603
|
+
vec3 acc = vec3(0.0);
|
|
4604
|
+
for (int j = 0; j < 4; j++) {
|
|
4605
|
+
for (int i = 0; i < 4; i++) {
|
|
4606
|
+
vec2 o = ((vec2(float(i), float(j)) + 0.5) / 4.0 - 0.5) * u_span;
|
|
4607
|
+
acc += toLinear(texture2D(u_source, v_uv + o).rgb);
|
|
4608
|
+
}
|
|
4609
|
+
}
|
|
4610
|
+
gl_FragColor = vec4(toSrgb(acc / 16.0), 1.0);
|
|
4611
|
+
}
|
|
4612
|
+
`;
|
|
4613
|
+
/** Backdrop blur, passes 2 and 3: one axis of a 25-tap Gaussian, in linear
|
|
4614
|
+
* light. Sigma is at most 4 small texels (below 2 only when blurPx < 2),
|
|
4615
|
+
* so +-12 taps reach 3 sigma. */
|
|
4616
|
+
const BLUR_PASS_FRAGMENT = `
|
|
4617
|
+
uniform sampler2D u_source;
|
|
4618
|
+
uniform vec2 u_step;
|
|
4619
|
+
uniform float u_sigma;
|
|
4620
|
+
void main() {
|
|
4621
|
+
vec3 acc = vec3(0.0);
|
|
4622
|
+
float wsum = 0.0;
|
|
4623
|
+
for (int i = -12; i <= 12; i++) {
|
|
4624
|
+
float x = float(i);
|
|
4625
|
+
float w = exp(-0.5 * x * x / (u_sigma * u_sigma));
|
|
4626
|
+
acc += w * toLinear(texture2D(u_source, v_uv + x * u_step).rgb);
|
|
4627
|
+
wsum += w;
|
|
4628
|
+
}
|
|
4629
|
+
gl_FragColor = vec4(toSrgb(acc / wsum), 1.0);
|
|
4630
|
+
}
|
|
4631
|
+
`;
|
|
4199
4632
|
const QUAD_VERTICES = new Float32Array([
|
|
4200
4633
|
-1,
|
|
4201
4634
|
-1,
|
|
@@ -4447,6 +4880,69 @@ function createStackRenderer(opts) {
|
|
|
4447
4880
|
}
|
|
4448
4881
|
cellTargetsUsed.clear();
|
|
4449
4882
|
}
|
|
4883
|
+
const blurTargets = /* @__PURE__ */ new Map();
|
|
4884
|
+
const blurTargetsUsed = /* @__PURE__ */ new Set();
|
|
4885
|
+
let blurDownProgram = null;
|
|
4886
|
+
let blurPassProgram = null;
|
|
4887
|
+
/**
|
|
4888
|
+
* `input` blurred by `blurRef` reference px. Done at 1/k resolution with
|
|
4889
|
+
* k chosen so sigma is at most 4 small texels (below 2 only when blurPx
|
|
4890
|
+
* < 2, where k floors to 1): the cost is the same at every output size,
|
|
4891
|
+
* and the small target is ~2 * ref / blur texels whatever the output
|
|
4892
|
+
* scale, so preview and export blur alike.
|
|
4893
|
+
*/
|
|
4894
|
+
function blurred(input, blurRef) {
|
|
4895
|
+
const blurPx = blurRef * outputScale(canvas.width, canvas.height);
|
|
4896
|
+
const k = Math.max(1, Math.floor(blurPx / 2));
|
|
4897
|
+
const w = Math.max(1, Math.round(canvas.width / k));
|
|
4898
|
+
const h = Math.max(1, Math.round(canvas.height / k));
|
|
4899
|
+
const key = `${w}x${h}`;
|
|
4900
|
+
blurTargetsUsed.add(key);
|
|
4901
|
+
let pair = blurTargets.get(key);
|
|
4902
|
+
if (!pair) {
|
|
4903
|
+
pair = [createTarget(gl, w, h, "linear"), createTarget(gl, w, h, "linear")];
|
|
4904
|
+
blurTargets.set(key, pair);
|
|
4905
|
+
}
|
|
4906
|
+
const [a, b] = pair;
|
|
4907
|
+
blurDownProgram ??= createProgram(gl, VERTEX_SHADER, BASE_UNIFORMS + COLOR_MATH + BLUR_DOWN_FRAGMENT);
|
|
4908
|
+
blurPassProgram ??= createProgram(gl, VERTEX_SHADER, BASE_UNIFORMS + COLOR_MATH + BLUR_PASS_FRAGMENT);
|
|
4909
|
+
bindOutput(a);
|
|
4910
|
+
useProgram(blurDownProgram, [w, h]);
|
|
4911
|
+
bindTexture(1, input.texture);
|
|
4912
|
+
gl.uniform1i(blurDownProgram.loc("u_source"), 1);
|
|
4913
|
+
gl.uniform2f(blurDownProgram.loc("u_span"), k / canvas.width, k / canvas.height);
|
|
4914
|
+
draw();
|
|
4915
|
+
const passes = [[
|
|
4916
|
+
a,
|
|
4917
|
+
b,
|
|
4918
|
+
1 / w,
|
|
4919
|
+
0
|
|
4920
|
+
], [
|
|
4921
|
+
b,
|
|
4922
|
+
a,
|
|
4923
|
+
0,
|
|
4924
|
+
1 / h
|
|
4925
|
+
]];
|
|
4926
|
+
for (const [src, dst, dx, dy] of passes) {
|
|
4927
|
+
bindOutput(dst);
|
|
4928
|
+
useProgram(blurPassProgram, [w, h]);
|
|
4929
|
+
bindTexture(1, src.texture);
|
|
4930
|
+
gl.uniform1i(blurPassProgram.loc("u_source"), 1);
|
|
4931
|
+
gl.uniform2f(blurPassProgram.loc("u_step"), dx, dy);
|
|
4932
|
+
gl.uniform1f(blurPassProgram.loc("u_sigma"), blurPx / k);
|
|
4933
|
+
draw();
|
|
4934
|
+
}
|
|
4935
|
+
return a;
|
|
4936
|
+
}
|
|
4937
|
+
function pruneBlurTargets() {
|
|
4938
|
+
for (const [key, pair] of blurTargets) {
|
|
4939
|
+
if (blurTargetsUsed.has(key)) continue;
|
|
4940
|
+
deleteTarget(gl, pair[0]);
|
|
4941
|
+
deleteTarget(gl, pair[1]);
|
|
4942
|
+
blurTargets.delete(key);
|
|
4943
|
+
}
|
|
4944
|
+
blurTargetsUsed.clear();
|
|
4945
|
+
}
|
|
4450
4946
|
let timeSec = 0;
|
|
4451
4947
|
function bindOutput(target) {
|
|
4452
4948
|
gl.bindFramebuffer(gl.FRAMEBUFFER, target ? target.framebuffer : null);
|
|
@@ -4639,7 +5135,6 @@ function createStackRenderer(opts) {
|
|
|
4639
5135
|
let input = null;
|
|
4640
5136
|
active.forEach((state, i) => {
|
|
4641
5137
|
const last = i === active.length - 1;
|
|
4642
|
-
const outIndex = input === frameTargets[0] ? 1 : 0;
|
|
4643
5138
|
const overGenerator = input === null && source.kind === "generator";
|
|
4644
5139
|
syncTextures(state);
|
|
4645
5140
|
if (state.def.grid) {
|
|
@@ -4647,6 +5142,15 @@ function createStackRenderer(opts) {
|
|
|
4647
5142
|
const [cellIn, cellOut] = cellTargetPair(grid);
|
|
4648
5143
|
if (input === null) renderSourceToCells(grid, cellIn);
|
|
4649
5144
|
else downsampleToCells(input, grid, cellIn);
|
|
5145
|
+
const pic = state.def.grid.picture;
|
|
5146
|
+
const want = pic === true ? { blur: 0 } : pic ? pic(state.params) : null;
|
|
5147
|
+
if (want && input === null) {
|
|
5148
|
+
input = frameTarget(0);
|
|
5149
|
+
renderSourceToFrame(input);
|
|
5150
|
+
}
|
|
5151
|
+
let picture = want ? input : null;
|
|
5152
|
+
if (picture && want && want.blur > 0) picture = blurred(picture, want.blur);
|
|
5153
|
+
const outIndex = input === frameTargets[0] ? 1 : 0;
|
|
4650
5154
|
const cellProgram = state.cell;
|
|
4651
5155
|
bindOutput(cellOut);
|
|
4652
5156
|
useProgram(cellProgram, frameSize);
|
|
@@ -4661,6 +5165,10 @@ function createStackRenderer(opts) {
|
|
|
4661
5165
|
applyEffectUniforms(drawProgram, state, grid, overGenerator);
|
|
4662
5166
|
bindTexture(2, cellOut.texture);
|
|
4663
5167
|
gl.uniform1i(drawProgram.loc("u_cells"), 2);
|
|
5168
|
+
if (picture) {
|
|
5169
|
+
bindTexture(1, picture.texture);
|
|
5170
|
+
gl.uniform1i(drawProgram.loc("u_picture"), 1);
|
|
5171
|
+
}
|
|
4664
5172
|
draw();
|
|
4665
5173
|
input = out;
|
|
4666
5174
|
} else {
|
|
@@ -4680,6 +5188,7 @@ function createStackRenderer(opts) {
|
|
|
4680
5188
|
}
|
|
4681
5189
|
});
|
|
4682
5190
|
pruneCellTargets();
|
|
5191
|
+
pruneBlurTargets();
|
|
4683
5192
|
}
|
|
4684
5193
|
function dispose() {
|
|
4685
5194
|
for (const state of layers) disposeLayer(state);
|
|
@@ -4688,6 +5197,8 @@ function createStackRenderer(opts) {
|
|
|
4688
5197
|
mediaFrameProgram?.dispose();
|
|
4689
5198
|
mediaCellProgram?.dispose();
|
|
4690
5199
|
downsampleProgram?.dispose();
|
|
5200
|
+
blurDownProgram?.dispose();
|
|
5201
|
+
blurPassProgram?.dispose();
|
|
4691
5202
|
deleteTarget(gl, frameTargets[0]);
|
|
4692
5203
|
deleteTarget(gl, frameTargets[1]);
|
|
4693
5204
|
frameTargets = [null, null];
|
|
@@ -4696,6 +5207,11 @@ function createStackRenderer(opts) {
|
|
|
4696
5207
|
deleteTarget(gl, pair[1]);
|
|
4697
5208
|
}
|
|
4698
5209
|
cellTargets.clear();
|
|
5210
|
+
for (const pair of blurTargets.values()) {
|
|
5211
|
+
deleteTarget(gl, pair[0]);
|
|
5212
|
+
deleteTarget(gl, pair[1]);
|
|
5213
|
+
}
|
|
5214
|
+
blurTargets.clear();
|
|
4699
5215
|
gl.deleteTexture(mediaTexture);
|
|
4700
5216
|
gl.deleteTexture(paletteTexture);
|
|
4701
5217
|
gl.deleteBuffer(quadBuffer);
|
|
@@ -5024,4 +5540,4 @@ function createRenderer(opts) {
|
|
|
5024
5540
|
}
|
|
5025
5541
|
|
|
5026
5542
|
//#endregion
|
|
5027
|
-
export { ascii, beam, bloom, buildPaletteRamp, burst, createRenderer, createStackRenderer, dither, dune, effects, flow, getEffect, getShader, glint, gridFor, halftone, halo, mountGradient, mountStack, nacre, pixelate, renderGradientFrame, renderStackFrame, shaders, silk, strata, whorl };
|
|
5543
|
+
export { ascii, beam, bloom, buildPaletteRamp, burst, createRenderer, createStackRenderer, dither, dune, effects, flow, getEffect, getShader, glint, gridFor, halftone, halo, mountGradient, mountStack, nacre, pixelate, renderGradientFrame, renderStackFrame, shaders, silk, strata, tint, whorl };
|