p5
Version:
[](https://www.npmjs.com/package/p5)
331 lines (289 loc) • 10.9 kB
JavaScript
import { T as Texture, e as filterOpaqueFrag, g as filterPosterizeFrag, h as filterDilateFrag, j as filterGrayFrag, k as filterErodeFrag, n as filterThresholdFrag, o as filterInvertFrag, q as filterBlurFrag, S as Shader, w as webgl2CompatibilityShader, u as filterBaseVert, v as filterBaseFrag, x as filterShaderVert } from '../rendering-CvUVN-Vb.js';
import { I as Image } from '../p5.Renderer-R23xoC7s.js';
import { z as WEBGL2, K as OPAQUE, U as POSTERIZE, V as DILATE, X as GRAY, Y as ERODE, Z as THRESHOLD, _ as INVERT, $ as BLUR, e as CORNER, u as BLEND, a0 as WEBGL } from '../constants-BRcElHU3.js';
import { filterParamDefaults } from './const.js';
import '../creating_reading-Cr8L2Jnm.js';
import 'colorjs.io/fn';
import '../color/color_spaces/hsb.js';
import '../dom/p5.Element.js';
import '../dom/p5.File.js';
import '../io/p5.XML.js';
import '../dom/p5.MediaElement.js';
import '../shape/2d_primitives.js';
import '../core/helpers.js';
import '../shape/attributes.js';
import '../shape/curves.js';
import '../shape/vertex.js';
import '../color/setting.js';
import 'omggif';
import '../io/csv.js';
import '../io/utilities.js';
import 'file-saver';
import 'gifenc';
import './pixels.js';
import './filters.js';
import '../core/transform.js';
import '../webgl/GeometryBuilder.js';
import '../math/p5.Matrix.js';
import '../math/Matrices/Matrix.js';
import '../math/p5.Vector.js';
import '../math/Matrices/MatrixInterface.js';
import '../webgl/p5.Geometry.js';
import '../webgl/p5.DataArray.js';
import '../webgl/p5.Quat.js';
import '../webgl/p5.RenderBuffer.js';
import '../webgl/ShapeBuilder.js';
import 'libtess';
import '../webgl/GeometryBufferCache.js';
import '../shape/custom_shapes.js';
import '../math/trigonometry.js';
import '../core/States.js';
class FilterRenderer2D {
/**
* Creates a new FilterRenderer2D instance.
* @param {p5} pInst - The p5.js instance.
*/
constructor(pInst) {
this.pInst = pInst;
// Create a canvas for applying WebGL-based filters
this.canvas = document.createElement('canvas');
this.canvas.width = pInst.width;
this.canvas.height = pInst.height;
// Initialize the WebGL context
let webglVersion = WEBGL2;
this.gl = this.canvas.getContext('webgl2');
if (!this.gl) {
webglVersion = WEBGL;
this.gl = this.canvas.getContext('webgl');
}
if (!this.gl) {
console.error("WebGL not supported, cannot apply filter.");
return;
}
// Minimal renderer object required by p5.Shader and p5.Texture
this._renderer = {
GL: this.gl,
registerEnabled: new Set(),
_curShader: null,
_emptyTexture: null,
webglVersion,
states: {
textureWrapX: this.gl.CLAMP_TO_EDGE,
textureWrapY: this.gl.CLAMP_TO_EDGE,
},
_arraysEqual: (a, b) => JSON.stringify(a) === JSON.stringify(b),
_getEmptyTexture: () => {
if (!this._emptyTexture) {
const im = new Image(1, 1);
im.set(0, 0, 255);
this._emptyTexture = new Texture(this._renderer, im);
}
return this._emptyTexture;
},
};
this._baseFilterShader = undefined;
// Store the fragment shader sources
this.filterShaderSources = {
[BLUR]: filterBlurFrag,
[INVERT]: filterInvertFrag,
[THRESHOLD]: filterThresholdFrag,
[ERODE]: filterErodeFrag,
[GRAY]: filterGrayFrag,
[DILATE]: filterDilateFrag,
[POSTERIZE]: filterPosterizeFrag,
[OPAQUE]: filterOpaqueFrag,
};
// Store initialized shaders for each operation
this.filterShaders = {};
// These will be set by setOperation
this.operation = null;
this.filterParameter = 1;
this.customShader = null;
this._shader = null;
// Create buffers once
this.vertexBuffer = this.gl.createBuffer();
this.texcoordBuffer = this.gl.createBuffer();
// Set up the vertices and texture coordinates for a full-screen quad
this.vertices = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);
this.texcoords = new Float32Array([0, 1, 1, 1, 0, 0, 1, 0]);
// Upload vertex data once
this._bindBufferData(this.vertexBuffer, this.gl.ARRAY_BUFFER, this.vertices);
// Upload texcoord data once
this._bindBufferData(this.texcoordBuffer, this.gl.ARRAY_BUFFER, this.texcoords);
}
_webGL2CompatibilityPrefix(shaderType, floatPrecision) {
let code = "";
if (this._renderer.webglVersion === WEBGL2) {
code += "#version 300 es\n#define WEBGL2\n";
}
if (shaderType === "vert") {
code += "#define VERTEX_SHADER\n";
} else if (shaderType === "frag") {
code += "#define FRAGMENT_SHADER\n";
}
if (floatPrecision) {
code += `precision ${floatPrecision} float;\n`;
}
return code;
}
baseFilterShader() {
if (!this._baseFilterShader) {
this._baseFilterShader = new Shader(
this._renderer,
this._webGL2CompatibilityPrefix("vert", "highp") +
webgl2CompatibilityShader +
filterBaseVert,
this._webGL2CompatibilityPrefix("frag", "highp") +
webgl2CompatibilityShader +
filterBaseFrag,
{
vertex: {},
fragment: {
"vec4 getColor": `(FilterInputs inputs, in sampler2D canvasContent) {
return getTexture(canvasContent, inputs.texCoord);
}`,
},
}
);
}
return this._baseFilterShader;
}
/**
* Set the current filter operation and parameter. If a customShader is provided,
* that overrides the operation-based shader.
* @param {String} operation - The filter operation type (e.g., constants.BLUR).
* @param {Number} filterParameter - The strength of the filter.
* @param {p5.Shader} customShader - Optional custom shader.
*/
setOperation(operation, filterParameter, customShader = null) {
this.operation = operation;
this.filterParameter = filterParameter;
let useDefaultParam = operation in filterParamDefaults && filterParameter === undefined;
if (useDefaultParam) {
this.filterParameter = filterParamDefaults[operation];
}
this.customShader = customShader;
this._initializeShader();
}
/**
* Initializes or retrieves the shader program for the current operation.
* If a customShader is provided, that is used.
* Otherwise, returns a cached shader if available, or creates a new one, caches it, and sets it as current.
*/
_initializeShader() {
if (this.customShader) {
this._shader = this.customShader;
return;
}
if (!this.operation) {
console.error("No operation set for FilterRenderer2D, cannot initialize shader.");
return;
}
// If we already have a compiled shader for this operation, reuse it
if (this.filterShaders[this.operation]) {
this._shader = this.filterShaders[this.operation];
return;
}
const fragShaderSrc = this.filterShaderSources[this.operation];
if (!fragShaderSrc) {
console.error("No shader available for this operation:", this.operation);
return;
}
// Create and store the new shader
const newShader = new Shader(this._renderer, filterShaderVert, fragShaderSrc);
this.filterShaders[this.operation] = newShader;
this._shader = newShader;
}
/**
* Binds a buffer to the drawing context
* when passed more than two arguments it also updates or initializes
* the data associated with the buffer
*/
_bindBufferData(buffer, target, values) {
const gl = this.gl;
gl.bindBuffer(target, buffer);
gl.bufferData(target, values, gl.STATIC_DRAW);
}
get canvasTexture() {
if (!this._canvasTexture) {
this._canvasTexture = new Texture(this._renderer, this.pInst.wrappedElt);
}
return this._canvasTexture;
}
/**
* Prepares and runs the full-screen quad draw call.
*/
_renderPass() {
const gl = this.gl;
this._shader.bindShader();
const pixelDensity = this.pInst.pixelDensity ? this.pInst.pixelDensity() : 1;
const texelSize = [
1 / (this.pInst.width * pixelDensity),
1 / (this.pInst.height * pixelDensity)
];
const canvasTexture = this.canvasTexture;
// Set uniforms for the shader
this._shader.setUniform('tex0', canvasTexture);
this._shader.setUniform('texelSize', texelSize);
this._shader.setUniform('canvasSize', [this.pInst.width, this.pInst.height]);
this._shader.setUniform('radius', Math.max(1, this.filterParameter));
this._shader.setUniform('filterParameter', this.filterParameter);
this._shader.setDefaultUniforms();
this.pInst.states.setValue('rectMode', CORNER);
this.pInst.states.setValue('imageMode', CORNER);
this.pInst.blendMode(BLEND);
this.pInst.resetMatrix();
const identityMatrix = [1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1];
this._shader.setUniform('uModelViewMatrix', identityMatrix);
this._shader.setUniform('uProjectionMatrix', identityMatrix);
// Bind and enable vertex attributes
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
this._shader.enableAttrib(this._shader.attributes.aPosition, 2);
gl.bindBuffer(gl.ARRAY_BUFFER, this.texcoordBuffer);
this._shader.enableAttrib(this._shader.attributes.aTexCoord, 2);
this._shader.bindTextures();
this._shader.disableRemainingAttributes();
// Draw the quad
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
// Unbind the shader
this._shader.unbindShader();
}
/**
* Applies the current filter operation. If the filter requires multiple passes (e.g. blur),
* it handles those internally. Make sure setOperation() has been called before applyFilter().
*/
applyFilter() {
if (!this._shader) {
console.error("Cannot apply filter: shader not initialized.");
return;
}
this.pInst.push();
this.pInst.resetMatrix();
// For blur, we typically do two passes: one horizontal, one vertical.
if (this.operation === BLUR && !this.customShader) {
// Horizontal pass
this._shader.setUniform('direction', [1, 0]);
this._renderPass();
// Draw the result onto itself
this.pInst.clear();
this.pInst.drawingContext.drawImage(this.canvas, 0, 0, this.pInst.width, this.pInst.height);
// Vertical pass
this._shader.setUniform('direction', [0, 1]);
this._renderPass();
this.pInst.clear();
this.pInst.drawingContext.drawImage(this.canvas, 0, 0, this.pInst.width, this.pInst.height);
} else {
// Single-pass filters
this._renderPass();
this.pInst.clear();
// con
this.pInst.blendMode(BLEND);
this.pInst.drawingContext.drawImage(this.canvas, 0, 0, this.pInst.width, this.pInst.height);
}
this.pInst.pop();
}
}
export { FilterRenderer2D as default };