1const e=`/** 2 * THE TREATMENTS â five display passes over \`pictureField.ts\`'s one rig. 3 * 4 * Each entry is a fragment shader, its config, its knob schema and its 5 * uniform upload â the engine mounts whichever one the catalogue names. The 6 * split is the same as fluid.ts / films.ts next door, and for the same 7 * reason: five components for roughly the price of one, and a sixth is a 8 * shader, not an afternoon of plumbing. 9 * 10 * Shared vocabulary (uploaded by the engine, identical in every pass): 11 * uTex the photo, Y-flipped so its uv is GL-up like vUv 12 * uCover object-fit: cover scale pair â \`pic()\` below applies it 13 * uHand raw pointer, frame uv, GL-up 14 * uF1..3 eased followers, fast â slow, taus chosen per treatment 15 * uRes/uDpr device-pixel geometry, for anything that must be DPR-stable 16 * 17 * Individual provenance notes sit on each pass. None of these is a port: 18 * the dither matrix is the standard published Bayer construction, the CMYK 19 * separation is the naive textbook one, and the rest is this file's own 20 * arithmetic. 21 */ 22import type { Param, ParamValue } from "../params"; 23import { hexToRgb } from "../params"; 24 25export type TreatmentName = "grade" | "dither" | "tear" | "chroma" | "rosette"; 26 27type UniformSetter = ( 28 gl: WebGL2RenderingContext, 29 U: (name: string) => WebGLUniformLocation | null, 30 P: Record<string, ParamValue>, 31) => void; 32 33export type Treatment = { 34 frag: string; 35 defaults: Record<string, ParamValue>; 36 /** Follower time constants, seconds â fast, mid, slow. 0 pins to the hand. */ 37 taus: [number, number, number]; 38 /** Where reduced motion parks the clock. */ 39 reduceT: number; 40 params(P: Record<string, ParamValue>): Param[]; 41 upload: UniformSetter; 42}; 43 44/* ââ shared GLSL ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 45 46const HEAD = [ 47 "#version 300 es", 48 "precision highp float;", 49 "in vec2 vUv;", 50 "out vec4 outColor;", 51 "uniform sampler2D uTex;", 52 "uniform float uTime, uDpr, uSeen;", 53 "uniform vec2 uRes, uCover, uHand, uF1, uF2, uF3;", 54 /* cover-fit lookup â the one line every pass shares */ 55 "vec2 cov(vec2 uv){ return 0.5 + (uv - 0.5) * uCover; }", 56 "vec3 pic(vec2 uv){ return texture(uTex, cov(uv)).rgb; }", 57 "float luma(vec3 c){ return dot(c, vec3(0.2126, 0.7152, 0.0722)); }", 58]; 59 60const uc = (gl: WebGL2RenderingContext, loc: WebGLUniformLocation | null, hex: string) => { 61 const [r, g, b] = hexToRgb(String(hex)); 62 gl.uniform3f(loc, r, g, b); 63}; 64 65/* ââ 1 · GRADE â a real gradient map, and a hand that returns the truth ââââ */ 66 67const GRADE_FRAG = [ 68 ...HEAD, 69 "uniform vec3 uShadow, uMid, uHigh;", 70 "uniform float uThree, uGamma, uReveal, uSoft, uGrain;", 71 "float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1,311.7)))*43758.5453); }", 72 "void main(){", 73 " vec3 src = pic(vUv);", 74 " float l = pow(luma(src), uGamma);", 75 /* The map: two stops, or three with the mid engaged. The mixes are done in 76 the working space directly â a gradient map is a LOOKUP, and looking up 77 through a smoothstep per span is what keeps the transitions from banding 78 the way a single mix over 0..1 does. */ 79 " vec3 lo = mix(uShadow, uThree > 0.5 ? uMid : uHigh, smoothstep(0.0, uThree > 0.5 ? 0.55 : 1.0, l));", 80 " vec3 mapped = uThree > 0.5 ? mix(lo, uHigh, smoothstep(0.45, 1.0, l)) : lo;", 81 /* Print grain, in device px, keyed to the mapped tone's midband so it reads 82 as paper, not as sensor noise. */ 83 " float gr = (hash(floor(gl_FragCoord.xy / uDpr)) - 0.5) * uGrain * (0.5 - abs(l - 0.5));", 84 " mapped += gr;", 85 /* THE REVEAL: inside the lagged circle the true photograph returns. The 86 radius is in frame heights and the distance is aspect-corrected, so the 87 window is a circle on any stage. uSeen gates it â before the first 88 pointer event there is no hole parked in the middle of the frame. */ 89 " float aspect = uRes.x / max(uRes.y, 1.0);", 90 " float d = length((vUv - uF2) * vec2(aspect, 1.0));", 91 " float win = uSeen * (1.0 - smoothstep(uReveal * (1.0 - uSoft), uReveal, d));", 92 " outColor = vec4(mix(mapped, src, uReveal <= 0.001 ? 0.0 : win), 1.0);", 93 "}", 94].join("\\n"); 95 96const grade: Treatment = { 97 frag: GRADE_FRAG, 98 defaults: { 99 shadow: "#101b3c", 100 mid: "#b3472f", 101 high: "#f2ead8", 102 three: false, 103 gamma: 1.0, 104 reveal: 0.28, 105 soft: 0.45, 106 grain: 0.06, 107 }, 108 taus: [0.05, 0.16, 0.4], 109 reduceT: 2, 110 params: (P) => [ 111 { key: "shadow", label: "Shadow ink", kind: "color", value: String(P.shadow) }, 112 { key: "high", label: "Highlight", kind: "color", value: String(P.high) }, 113 { 114 key: "three", 115 label: "Third stop", 116 kind: "toggle", 117 value: Boolean(P.three), 118 hint: "Adds the mid ink â duotone becomes tritone.", 119 }, 120 { key: "mid", label: "Mid ink", kind: "color", value: String(P.mid) }, 121 { 122 key: "gamma", 123 label: "Tone", 124 kind: "range", 125 min: 0.4, 126 max: 2.4, 127 step: 0.05, 128 value: Number(P.gamma), 129 hint: "Where the map's pivot sits. Low lifts the shadows into the mid ink.", 130 }, 131 { 132 group: "Reveal", 133 key: "reveal", 134 label: "Window", 135 kind: "range", 136 min: 0, 137 max: 0.7, 138 step: 0.01, 139 value: Number(P.reveal),
140 hint: "The lagged circle where the true photograph returns. 0 turns it off.", 141 }, 142 { 143 group: "Reveal", 144 key: "soft", 145 label: "Feather", 146 kind: "range", 147 min: 0.05, 148 max: 0.9, 149 step: 0.01, 150 value: Number(P.soft), 151 }, 152 { 153 group: "Reveal", 154 key: "grain", 155 label: "Grain", 156 kind: "range", 157 min: 0, 158 max: 0.25, 159 step: 0.01, 160 value: Number(P.grain), 161 }, 162 ], 163 upload: (gl, U, P) => { 164 uc(gl, U("uShadow"), String(P.shadow)); 165 uc(gl, U("uMid"), String(P.mid)); 166 uc(gl, U("uHigh"), String(P.high)); 167 gl.uniform1f(U("uThree"), P.three ? 1 : 0); 168 gl.uniform1f(U("uGamma"), Number(P.gamma)); 169 gl.uniform1f(U("uReveal"), Number(P.reveal)); 170 gl.uniform1f(U("uSoft"), Number(P.soft)); 171 gl.uniform1f(U("uGrain"), Number(P.grain)); 172 }, 173}; 174 175/* ââ 2 · DITHER â Bayer, done right ââââââââââââââââââââââââââââââââââââââââ 176 The matrix is the standard recursive construction, written out at order 8; 177 thresholds are (b + 0.5)/64 so the average error is zero (the +0.5 is the 178 half everyone forgets, and without it the whole picture darkens by half a 179 level). Cells are DEVICE pixels divided by uDpr·scale, so a "2px" dot is 180 the same physical size on every screen â an ordered dither whose cell size 181 floats with DPR reads as two different treatments on two laptops. */ 182 183const DITHER_FRAG = [ 184 ...HEAD, 185 "uniform float uScale, uLevels, uContrast, uMono;", 186 "uniform vec3 uInk, uPaper;", 187 "const int B8[64] = int[64](", 188 " 0,32, 8,40, 2,34,10,42,", 189 " 48,16,56,24,50,18,58,26,", 190 " 12,44, 4,36,14,46, 6,38,", 191 " 60,28,52,20,62,30,54,22,", 192 " 3,35,11,43, 1,33, 9,41,", 193 " 51,19,59,27,49,17,57,25,", 194 " 15,47, 7,39,13,45, 5,37,", 195 " 63,31,55,23,61,29,53,21);", 196 "void main(){", 197 " vec2 cell = floor(gl_FragCoord.xy / (uScale * uDpr));", 198 " ivec2 m = ivec2(mod(cell, 8.0));", 199 " float th = (float(B8[m.y * 8 + m.x]) + 0.5) / 64.0;", 200 " vec3 src = pic(vUv);", 201 " src = (src - 0.5) * uContrast + 0.5;", 202 " float L = max(uLevels - 1.0, 1.0);", 203 " if (uMono > 0.5) {", 204 /* Newspaper mode: one channel, two inks. */ 205 " float v = clamp(luma(src), 0.0, 1.0);", 206 " float q = floor(v * L + th) / L;", 207 " outColor = vec4(mix(uInk, uPaper, q), 1.0);", 208 " } else {", 209 /* Colour mode: each channel quantised against the same threshold â the 210 classic RGB ordered dither, banding traded for the pattern. */ 211 " vec3 q = floor(clamp(src, 0.0, 1.0) * L + th) / L;", 212 " outColor = vec4(q, 1.0);", 213 " }", 214 "}", 215].join("\\n"); 216 217const dither: Treatment = { 218 frag: DITHER_FRAG, 219 defaults: { 220 scale: 2, 221 levels: 2, 222 contrast: 1.15, 223 mono: true, 224 ink: "#12141c", 225 paper: "#f4f1e9", 226 }, 227 taus: [0, 0, 0], 228 reduceT: 0, 229 params: (P) => [ 230 { 231 key: "scale", 232 label: "Dot", 233 kind: "range", 234 min: 1, 235 max: 8, 236 step: 1, 237 value: Number(P.scale), 238 suffix: "px", 239 hint: "Cell size in physical pixels â DPR-stable, so it prints the same on every screen.", 240 }, 241 { 242 key: "levels", 243 label: "Levels", 244 kind: "range", 245 min: 2, 246 max: 6, 247 step: 1, 248 value: Number(P.levels), 249 hint: "Tones per channel before the pattern carries the rest. 2 is the woodcut.", 250 }, 251 { 252 key: "contrast", 253 label: "Contrast", 254 kind: "range", 255 min: 0.5, 256 max: 2, 257 step: 0.05, 258 value: Number(P.contrast), 259 }, 260 { 261 key: "mono", 262 label: "Two inks only", 263 kind: "toggle", 264 value: Boolean(P.mono), 265 hint: "On: luminance between Ink and Paper. Off: full-colour ordered dither.", 266 }, 267 { group: "Ink", key: "ink", label: "Ink", kind: "color", value: String(P.ink) }, 268 { group: "Ink", key: "paper", label: "Paper", kind: "color", value: String(P.paper) }, 269 ], 270 upload: (gl, U, P) => { 271 gl.uniform1f(U("uScale"), Number(P.scale)); 272 gl.uniform1f(U("uLevels"), Number(P.levels)); 273 gl.uniform1f(U("uContrast"), Number(P.contrast)); 274 gl.uniform1f(U("uMono"), P.mono ? 1 : 0); 275 uc(gl, U("uInk"), String(P.ink)); 276 uc(gl, U("uPaper"), String(P.paper)); 277 }, 278}; 279 280/* ââ 3 · TEAR â displacement gated by the image's own edges âââââââââââââââ 281 A Sobel pass finds the picture's contours live; displacement near the hand 282 is MULTIPLIED by that edge magnitude, so flat areas hold still and the 283 image slides apart along its own seams â a tear, not a smear. The 284 direction blends along-edge (the picture shears along a contour) with 285 across-edge (the seam opens). PAID: this is the one whose display pass IS 286 the product â no free entry displaces anything. */ 287 288const TEAR_FRAG = [ 289 ...HEAD, 290 "uniform float uStrength, uReach, uAlong, uAmbient, uFringe;", 291 "float lum(vec2 uv){ return luma(texture(uTex, uv).rgb); }", 292 "void main(){", 293 " vec2 cuv = cov(vUv);", 294 " vec2 tx = 1.0 / vec2(textureSize(uTex, 0));", 295 /* Sobel over the PICTURE's texels, not the frame's â edge width follows the 296 photograph, so the tear is the same feature at any stage size. */ 297 " float tl = lum(cuv + vec2(-tx.x, tx.y)), t = lum(cuv + vec2(0.0, tx.y)), tr = lum(cuv + tx);", 298 " float l = lum(cuv + vec2(-tx.x, 0.0)), r = lum(cuv + vec2(tx.x, 0.0));", 299 " float bl = lum(cuv - tx), b = lum(cuv - vec2(0.0, tx.y)), br = lum(cuv + vec2(tx.x, -tx.y));", 300 " float gx = (tr + 2.0*r + br) - (tl + 2.0*l + bl);", 301 " float gy = (tl + 2.0*t + tr) - (bl + 2.0*b + br);", 302 " vec2 grad = vec2(gx, gy);", 303 " float e = smoothstep(0.06, 0.5, length(grad));", 304 " vec2 nrm = length(grad) > 1e-5 ? normalize(grad) : vec2(0.0);", 305 " vec2 alongDir = vec2(nrm.y, -nrm.x);", 306 /* Drive: proximity to the fast follower, plus a slow ambient breath so the 307 card is alive with no pointer. The breath phases by position, so the 308 whole image never pumps in sync. */ 309 " float aspect = uRes.x / max(uRes.y, 1.0);", 310 " float d = length((vUv - uF1) * vec2(aspect, 1.0));", 311 " float near = uSeen * (1.0 - smoothstep(0.0, uReach, d));", 312 " float breath = uAmbient * (0.5 + 0.5 * sin(uTime * 0.8 + (cuv.x + cuv.y) * 9.0));", 313 " float amt = e * (near * uStrength + breath * 0.012);", 314 " vec2 dir = mix(nrm, alongDir, uAlong);", 315 " vec2 off = dir * amt;", 316 /* The razor fringe: each channel crosses the seam at a slightly different
317 width. Signed, so it can darken as well as lighten. */ 318 " float fr = uFringe * amt;", 319 " vec3 col;", 320 " col.r = texture(uTex, cuv + off * (1.0 + fr * 12.0)).r;", 321 " col.g = texture(uTex, cuv + off).g;", 322 " col.b = texture(uTex, cuv + off * (1.0 - fr * 12.0)).b;", 323 " outColor = vec4(col, 1.0);", 324 "}", 325].join("\\n"); 326 327const tear: Treatment = { 328 frag: TEAR_FRAG, 329 defaults: { strength: 0.05, reach: 0.34, along: 0.65, ambient: 0.55, fringe: 0.35 }, 330 taus: [0.09, 0.25, 0.5], 331 reduceT: 1.2, 332 params: (P) => [ 333 { 334 key: "strength", 335 label: "Tear", 336 kind: "range", 337 min: 0, 338 max: 0.14, 339 step: 0.002, 340 value: Number(P.strength), 341 hint: "How far the picture slides along its seams under the hand.", 342 }, 343 { 344 key: "reach", 345 label: "Reach", 346 kind: "range", 347 min: 0.1, 348 max: 0.8, 349 step: 0.01, 350 value: Number(P.reach), 351 }, 352 { 353 key: "along", 354 label: "Shear â open", 355 kind: "range", 356 min: 0, 357 max: 1, 358 step: 0.02, 359 value: Number(P.along), 360 hint: "0 opens the seam apart; 1 shears the picture along it.", 361 }, 362 { 363 key: "ambient", 364 label: "Breath", 365 kind: "range", 366 min: 0, 367 max: 1.5, 368 step: 0.05, 369 value: Number(P.ambient), 370 hint: "A slow edge shimmer with no pointer â what a touch screen sees.", 371 }, 372 { 373 key: "fringe", 374 label: "Fringe", 375 kind: "range", 376 min: 0, 377 max: 1, 378 step: 0.02, 379 value: Number(P.fringe), 380 hint: "Channel split across the seam. Signed â it darkens as well as lightens.", 381 }, 382 ], 383 upload: (gl, U, P) => { 384 gl.uniform1f(U("uStrength"), Number(P.strength)); 385 gl.uniform1f(U("uReach"), Number(P.reach)); 386 gl.uniform1f(U("uAlong"), Number(P.along)); 387 gl.uniform1f(U("uAmbient"), Number(P.ambient)); 388 gl.uniform1f(U("uFringe"), Number(P.fringe)); 389 }, 390}; 391 392/* ââ 4 · CHROMA DRAG â a split that lags correctly âââââââââââââââââââââââââ 393 The genre effect offsets R and B by ±velocity, which smears symmetrically 394 and snaps back the instant the input stops â the tell that it is a filter. 395 Here each channel samples where its own FOLLOWER is: three copies of the 396 picture chasing the hand at three time constants. The smear is asymmetric 397 while moving (blue furthest behind), collapses to zero exactly as the 398 followers arrive, and cannot overshoot, because nothing here is velocity â 399 it is three positions converging. */ 400 401const CHROMA_FRAG = [ 402 ...HEAD, 403 "uniform float uGain, uBend;", 404 "void main(){", 405 " float aspect = uRes.x / max(uRes.y, 1.0);", 406 /* Each channel's world is displaced by where its follower TRAILS the hand. 407 At rest all three sit on the hand and this is the untouched photo. */ 408 " vec2 oR = (uF1 - uHand) * uGain;", 409 " vec2 oG = (uF2 - uHand) * uGain;", 410 " vec2 oB = (uF3 - uHand) * uGain;", 411 /* uBend curves the smear: the offset is rotated slightly by its own length, 412 so a hard flick arcs rather than ruling a straight line. */ 413 " float bR = uBend * length(oR) * 6.0; mat2 mR = mat2(cos(bR), -sin(bR), sin(bR), cos(bR));", 414 " float bB = -uBend * length(oB) * 6.0; mat2 mB = mat2(cos(bB), -sin(bB), sin(bB), cos(bB));", 415 " float r = pic(vUv + mR * oR).r;", 416 " float g = pic(vUv + oG).g;", 417 " float b = pic(vUv + mB * oB).b;", 418 " outColor = vec4(r, g, b, 1.0);", 419 "}", 420].join("\\n"); 421 422const chroma: Treatment = { 423 frag: CHROMA_FRAG, 424 defaults: { gain: 0.55, bend: 0.35 }, 425 /* The whole component is these three numbers: the channels' time constants. 426 Spread wide enough to read as a tail, close enough to stay one image. */ 427 taus: [0.06, 0.14, 0.28], 428 reduceT: 0, 429 params: (P) => [ 430 { 431 key: "gain", 432 label: "Drag", 433 kind: "range", 434 min: 0, 435 max: 1.5, 436 step: 0.05, 437 value: Number(P.gain), 438 hint: "How far the channels trail the hand. At rest the photo is untouched.", 439 }, 440 { 441 key: "bend", 442 label: "Arc", 443 kind: "range", 444 min: 0, 445 max: 1, 446 step: 0.05,
447 value: Number(P.bend), 448 hint: "Rotates the smear by its own length, so a flick curves instead of ruling.", 449 }, 450 ], 451 upload: (gl, U, P) => { 452 gl.uniform1f(U("uGain"), Number(P.gain)); 453 gl.uniform1f(U("uBend"), Number(P.bend)); 454 }, 455}; 456 457/* ââ 5 · ROSETTE â a four-plate CMYK halftone, screens at press angles âââââ 458 The separation is the naive textbook one (K from max, UCR by division) and 459 the four screens sit at the classical 15/75/0/45 â the angles chosen by a 460 century of lithography because their moiré is the smallest and what 461 remains is the rosette itself. Dots grow by sqrt of coverage (a dot's AREA 462 is the tone), cells are physical pixels, and the inks multiply over paper 463 the way ink actually stacks. */ 464 465const ROSETTE_FRAG = [ 466 ...HEAD, 467 "uniform float uPitch, uGain, uJitter, uRegister, uLiftP;", 468 "uniform vec3 uPaper;", 469 "float screenDot(vec2 frag, float ang, float pitch, float v){", 470 " float c = cos(ang), s = sin(ang);", 471 " vec2 p = mat2(c, -s, s, c) * frag / pitch;", 472 " vec2 cellUv = fract(p) - 0.5;", 473 /* sqrt: coverage lives in area. 0.75 lets a full tone close the cell. */ 474 " float r = sqrt(clamp(v, 0.0, 1.0)) * 0.75;", 475 " float d = length(cellUv);", 476 " float aa = 0.7 / pitch;", 477 " return 1.0 - smoothstep(r - aa, r + aa, d);", 478 "}", 479 "void main(){", 480 " vec3 src = pic(vUv);", 481 /* Press exposure: lift toward paper before separating, the tone curve a 482 printer applies so a night photograph still ships visible paper and a 483 legible rosette instead of a closed black plate. Measured on the demo 484 set: without it the portrait's navy ground drives K past 0.8 and the 485 mids vanish. */ 486 " src = 1.0 - (1.0 - src) * (1.0 - uLiftP);", 487 " float k = 1.0 - max(src.r, max(src.g, src.b));", 488 " float den = max(1.0 - k, 1e-4);", 489 " float cy = (1.0 - src.r - k) / den;", 490 " float mg = (1.0 - src.g - k) / den;", 491 " float ye = (1.0 - src.b - k) / den;", 492 " float pitch = uPitch * uDpr;", 493 " vec2 frag = gl_FragCoord.xy;", 494 /* Misregistration: each plate slides a fraction of a cell off true. The 495 cheap-print charm, and 0 is a clean press. */ 496 " vec2 mis = vec2(uRegister * pitch * 0.35, -uRegister * pitch * 0.22);", 497 " float C = screenDot(frag + mis, radians(15.0 + uJitter), pitch, cy * uGain);", 498 " float M = screenDot(frag - mis, radians(75.0 - uJitter), pitch, mg * uGain);", 499 " float Y = screenDot(frag + mis.yx, radians( 0.0 + uJitter), pitch, ye * uGain);", 500 " float K = screenDot(frag, radians(45.0), pitch, k * uGain);", 501 /* Overprint: paper, then each ink multiplies down. */ 502 " vec3 col = uPaper;", 503 " col *= mix(vec3(1.0), vec3(0.0, 0.65, 0.95), C);", 504 " col *= mix(vec3(1.0), vec3(0.95, 0.0, 0.55), M);", 505 " col *= mix(vec3(1.0), vec3(1.0, 0.9, 0.0), Y);", 506 " col *= mix(vec3(1.0), vec3(0.08), K);", 507 " outColor = vec4(col, 1.0);", 508 "}", 509].join("\\n"); 510 511const rosette: Treatment = { 512 frag: ROSETTE_FRAG, 513 defaults: { pitch: 7, gain: 0.95, jitter: 0, register: 0.25, lift: 0.24, paper: "#f6f3ea" }, 514 taus: [0, 0, 0], 515 reduceT: 0, 516 params: (P) => [ 517 { 518 key: "lift", 519 label: "Exposure", 520 kind: "range", 521 min: 0, 522 max: 0.6, 523 step: 0.02, 524 value: Number(P.lift), 525 hint: "Lifts the source toward paper before separation â the press tone curve. 0 prints the shadows shut.", 526 }, 527 { 528 key: "pitch", 529 label: "Screen", 530 kind: "range", 531 min: 3, 532 max: 18, 533 step: 0.5, 534 value: Number(P.pitch), 535 suffix: "px", 536 hint: "Cell pitch in physical pixels. Small is magazine, large is billboard seen close.", 537 }, 538 { 539 key: "gain", 540 label: "Ink gain", 541 kind: "range", 542 min: 0.5, 543 max: 1.6, 544 step: 0.05, 545 value: Number(P.gain), 546 hint: "Dot growth on press. Past 1.2 the shadows close up, exactly like real gain.", 547 }, 548 { 549 key: "jitter", 550 label: "Angle drift", 551 kind: "range", 552 min: -10, 553 max: 10, 554 step: 0.5, 555 value: Number(P.jitter), 556 suffix: "°", 557 hint: "Nudges the C/M/Y screens off the press angles. The moiré you get is the real one.", 558 }, 559 { 560 key: "register", 561 label: "Misregister", 562 kind: "range", 563 min: 0, 564 max: 1, 565 step: 0.05, 566 value: Number(P.register), 567 hint: "Plates off true by a fraction of a cell â the cheap-print charm. 0 is a clean press.", 568 },
569 { key: "paper", label: "Paper", kind: "color", value: String(P.paper) }, 570 ], 571 upload: (gl, U, P) => { 572 gl.uniform1f(U("uPitch"), Number(P.pitch)); 573 gl.uniform1f(U("uGain"), Number(P.gain)); 574 gl.uniform1f(U("uJitter"), Number(P.jitter)); 575 gl.uniform1f(U("uRegister"), Number(P.register)); 576 gl.uniform1f(U("uLiftP"), Number(P.lift)); 577 uc(gl, U("uPaper"), String(P.paper)); 578 }, 579}; 580 581export const TREATMENTS: Record<TreatmentName, Treatment> = { 582 grade, 583 dither, 584 tear, 585 chroma, 586 rosette, 587}; 588`;export{e as default};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.