|
| 1 | +import { |
| 2 | + drawPass, ensureTexture, getGLCtx, getQuadVAO, glAvailable, |
| 3 | + linkProgram, readoutToCanvas, resizeGLCanvas, uploadSourceTexture, |
| 4 | + type Program, |
| 5 | +} from "gl"; |
| 6 | + |
| 7 | +// Per-channel threshold → 0 or 1. Alpha is not sRGB-encoded, so when |
| 8 | +// u_linearize=1 only RGB get transformed into linear before comparison. |
| 9 | +// Matches the JS path: `val > threshold` in the chosen colour space. |
| 10 | +const BINARIZE_FS = `#version 300 es |
| 11 | +precision highp float; |
| 12 | +in vec2 v_uv; |
| 13 | +out vec4 fragColor; |
| 14 | +
|
| 15 | +uniform sampler2D u_source; |
| 16 | +uniform vec4 u_threshold; // R, G, B, A thresholds in 0..1 |
| 17 | +uniform int u_linearize; // 1 = linearize RGB before threshold |
| 18 | +
|
| 19 | +float srgbToLinear(float c) { |
| 20 | + return c <= 0.04045 ? c / 12.92 : pow((c + 0.055) / 1.055, 2.4); |
| 21 | +} |
| 22 | +
|
| 23 | +void main() { |
| 24 | + vec4 c = texture(u_source, v_uv); |
| 25 | + if (u_linearize == 1) { |
| 26 | + c.r = srgbToLinear(c.r); |
| 27 | + c.g = srgbToLinear(c.g); |
| 28 | + c.b = srgbToLinear(c.b); |
| 29 | + } |
| 30 | + fragColor = vec4( |
| 31 | + c.r > u_threshold.r ? 1.0 : 0.0, |
| 32 | + c.g > u_threshold.g ? 1.0 : 0.0, |
| 33 | + c.b > u_threshold.b ? 1.0 : 0.0, |
| 34 | + c.a > u_threshold.a ? 1.0 : 0.0 |
| 35 | + ); |
| 36 | +} |
| 37 | +`; |
| 38 | + |
| 39 | +type Cache = { prog: Program }; |
| 40 | +let _cache: Cache | null = null; |
| 41 | +const initCache = (gl: WebGL2RenderingContext): Cache => { |
| 42 | + if (_cache) return _cache; |
| 43 | + _cache = { |
| 44 | + prog: linkProgram(gl, BINARIZE_FS, ["u_source", "u_threshold", "u_linearize"] as const), |
| 45 | + }; |
| 46 | + return _cache; |
| 47 | +}; |
| 48 | + |
| 49 | +export const binarizeGLAvailable = (): boolean => glAvailable(); |
| 50 | + |
| 51 | +export const renderBinarizeGL = ( |
| 52 | + source: HTMLCanvasElement | OffscreenCanvas, |
| 53 | + width: number, height: number, |
| 54 | + thresholdR: number, thresholdG: number, thresholdB: number, thresholdA: number, |
| 55 | + linearize: boolean, |
| 56 | +): HTMLCanvasElement | OffscreenCanvas | null => { |
| 57 | + const ctx = getGLCtx(); |
| 58 | + if (!ctx) return null; |
| 59 | + const { gl, canvas } = ctx; |
| 60 | + const cache = initCache(gl); |
| 61 | + const vao = getQuadVAO(gl); |
| 62 | + |
| 63 | + resizeGLCanvas(canvas, width, height); |
| 64 | + const sourceTex = ensureTexture(gl, "binarize:source", width, height); |
| 65 | + uploadSourceTexture(gl, sourceTex, source); |
| 66 | + |
| 67 | + drawPass(gl, null, width, height, cache.prog, () => { |
| 68 | + gl.activeTexture(gl.TEXTURE0); |
| 69 | + gl.bindTexture(gl.TEXTURE_2D, sourceTex.tex); |
| 70 | + gl.uniform1i(cache.prog.uniforms.u_source, 0); |
| 71 | + gl.uniform4f( |
| 72 | + cache.prog.uniforms.u_threshold, |
| 73 | + thresholdR / 255, thresholdG / 255, thresholdB / 255, thresholdA / 255, |
| 74 | + ); |
| 75 | + gl.uniform1i(cache.prog.uniforms.u_linearize, linearize ? 1 : 0); |
| 76 | + }, vao); |
| 77 | + |
| 78 | + return readoutToCanvas(canvas, width, height); |
| 79 | +}; |
0 commit comments