p5
Version:
[](https://www.npmjs.com/package/p5)
202 lines (172 loc) • 7.19 kB
JavaScript
import { ah as OPAQUE, ai as DILATE, aj as ERODE, ac as BLUR, ag as POSTERIZE, af as THRESHOLD, ak as INVERT, al as GRAY } from '../constants-DwbuOBz3.js';
/*
* Creates p5.strands filter shaders for cross-platform compatibility.
*
* NOTE: These work a little differently than p5.js web editor shaders work!
* Firstly, it uses instance mode, so we have to explicitly pass in context
* variables in an argument to your callback and as a second argument to `modify`.
* Secondly, always manually specify uniform names, as variable names will change
* in minified builds.
*/
function makeFilterShader(renderer, operation, p5) {
switch (operation) {
case GRAY:
return renderer.baseFilterShader().modify(({ p5 }) => {
p5.getColor((inputs, canvasContent) => {
const tex = p5.getTexture(canvasContent, inputs.texCoord);
// weighted grayscale with luminance values
const gray = p5.dot(tex.rgb, p5.vec3(0.2126, 0.7152, 0.0722));
return p5.vec4(gray, gray, gray, tex.a);
});
}, { p5 });
case INVERT:
return renderer.baseFilterShader().modify(({ p5 }) => {
p5.getColor((inputs, canvasContent) => {
const color = p5.getTexture(canvasContent, inputs.texCoord);
const invertedColor = p5.vec3(1.0) - color.rgb;
return p5.vec4(invertedColor, color.a);
});
}, { p5 });
case THRESHOLD:
return renderer.baseFilterShader().modify(({ p5 }) => {
const filterParameter = p5.uniformFloat('filterParameter');
p5.getColor((inputs, canvasContent) => {
const color = p5.getTexture(canvasContent, inputs.texCoord);
// weighted grayscale with luminance values
const gray = p5.dot(color.rgb, p5.vec3(0.2126, 0.7152, 0.0722));
const threshold = p5.floor(filterParameter * 255.0) / 255;
const blackOrWhite = p5.step(threshold, gray);
return p5.vec4(p5.vec3(blackOrWhite), color.a);
});
}, { p5 });
case POSTERIZE:
return renderer.baseFilterShader().modify(({ p5 }) => {
const filterParameter = p5.uniformFloat('filterParameter');
const quantize = (color, n) => {
// restrict values to N options/bins
// and floor each channel to nearest value
//
// eg. when N = 5, values = 0.0, 0.25, 0.50, 0.75, 1.0
// then quantize (0.1, 0.7, 0.9) -> (0.0, 0.5, 1.0)
color = color * n;
color = p5.floor(color);
color = color / (n - 1.0);
return color;
};
p5.getColor((inputs, canvasContent) => {
const color = p5.getTexture(canvasContent, inputs.texCoord);
const restrictedColor = quantize(color.rgb, filterParameter);
return p5.vec4(restrictedColor, color.a);
});
}, { p5 });
case BLUR:
return renderer.baseFilterShader().modify(({ p5 }) => {
const radius = p5.uniformFloat('radius');
const direction = p5.uniformVec2('direction');
// This isn't a real Gaussian weight, it's a quadratic weight
const quadWeight = (x, e) => {
return p5.pow(e - p5.abs(x), 2.0);
};
const random = (p) => {
let p3 = p5.fract(p.xyx * .1031);
p3 += p5.dot(p3, p3.yzx + 33.33);
return p5.fract((p3.x + p3.y) * p3.z);
};
p5.getColor((inputs, canvasContent) => {
const uv = inputs.texCoord;
// A reasonable maximum number of samples
const maxSamples = 64.0;
let numSamples = p5.floor(radius * 7.0);
if (p5.mod(numSamples, 2) == 0.0) {
numSamples++;
}
let avg = p5.vec4(0.0);
let total = 0.0;
// Calculate the spacing to avoid skewing if numSamples > maxSamples
let spacing = 1.0;
if (numSamples > maxSamples) {
spacing = numSamples / maxSamples;
numSamples = maxSamples;
}
const randomOffset = (spacing - 1.0) * p5.mix(-0.5, 0.5, random(uv * inputs.canvasSize));
for (let i = 0; i < numSamples; i++) {
const sample = i * spacing - (numSamples - 1.0) * 0.5 * spacing + randomOffset;
const sampleCoord = uv + p5.vec2(sample, sample) / inputs.canvasSize * direction;
const weight = quadWeight(sample, (numSamples - 1.0) * 0.5 * spacing);
const texSample = p5.getTexture(canvasContent, sampleCoord);
avg += weight * texSample * p5.vec4(
texSample.a, texSample.a, texSample.a, 1
);
total += weight;
}
const blended = avg / total;
return p5.vec4(
blended.r / blended.a,
blended.g / blended.a,
blended.b / blended.a,
blended.a
);
});
}, { p5 });
case ERODE:
return renderer.baseFilterShader().modify(({ p5 }) => {
const luma = (color) => {
return p5.dot(color.rgb, p5.vec3(0.2126, 0.7152, 0.0722));
};
p5.getColor((inputs, canvasContent) => {
const uv = inputs.texCoord;
let minColor = p5.getTexture(canvasContent, uv);
let minLuma = luma(minColor);
for (let x = -1; x <= 1; x++) {
for (let y = -1; y <= 1; y++) {
if (x != 0 || y != 0) {
const offset = p5.vec2(x, y) * inputs.texelSize;
const neighborColor = p5.getTexture(canvasContent, uv + offset);
const neighborLuma = luma(neighborColor);
if (neighborLuma < minLuma) {
minLuma = neighborLuma;
minColor = neighborColor;
}
}
}
}
return minColor;
});
}, { p5 });
case DILATE:
return renderer.baseFilterShader().modify(({ p5 }) => {
const luma = (color) => {
return p5.dot(color.rgb, p5.vec3(0.2126, 0.7152, 0.0722));
};
p5.getColor((inputs, canvasContent) => {
const uv = inputs.texCoord;
let maxColor = p5.getTexture(canvasContent, uv);
let maxLuma = luma(maxColor);
for (let x = -1; x <= 1; x++) {
for (let y = -1; y <= 1; y++) {
if (x != 0 || y != 0) {
const offset = p5.vec2(x, y) * inputs.texelSize;
const neighborColor = p5.getTexture(canvasContent, uv + offset);
const neighborLuma = luma(neighborColor);
if (neighborLuma > maxLuma) {
maxLuma = neighborLuma;
maxColor = neighborColor;
}
}
}
}
return maxColor;
});
}, { p5 });
case OPAQUE:
return renderer.baseFilterShader().modify(({ p5 }) => {
p5.getColor((inputs, canvasContent) => {
const color = p5.getTexture(canvasContent, inputs.texCoord);
return p5.vec4(color.rgb, 1.0);
});
}, { p5 });
default:
throw new Error(`Unknown filter: ${operation}`);
}
}
export { makeFilterShader };