iwmlib/lib/pixi/blurfilter.js

271 lines
7.9 KiB
JavaScript

/**
* A Gaussian blur filter. With this filter, you can blur an area of a PIXI.DisplayObject. This cannot
* be done with the PIXI.filters.BlurFilter (when you use the PIXI.filters.BlurFilter with
* an filter area, all pixels outside of the area are not displayed). Attention: The area of
* the filter is always in global scope, NOT relative to the PIXI.DisplayObject the filter
* is assigned to!
*
* @example
* // Create the app
* const app = new PIXIApp({
* view: canvas,
* width: 480,
* height: 270,
* transparent: false
* }).setup().run()
*
* // Add a video sprite
* const sprite = new PIXI.Sprite(PIXI.Texture.fromVideo("assets/blurfilter.mp4"))
* sprite.width = app.size.width
* sprite.height = app.size.height
* app.scene.addChild(sprite)
*
* // Create the filter and assign it to the scene
* const blurFilter = new BlurFilter(new PIXI.Rectangle(20, 20, 80, 60))
* app.scene.filters = [blurFilter]
*
* @class
* @extends PIXI.Filter
* @param {PIXI.Rectangle|PIXI.Circle|PIXI.DisplayObject} shape The area where the blur effect should be applied to. Relative to the
* canvas, NOT relative to the PIXI.DisplayObject where the blur effect is assigned to!
* @param {number} [blur=50] The strength of the blur.
*/
export default class BlurFilter extends PIXI.Filter {
constructor(shape, blur = 50) {
super()
const normalized = this.normalize(shape)
this.tiltShiftXFilter = new TiltShiftXFilter(normalized, blur)
this.tiltShiftYFilter = new TiltShiftYFilter(normalized, blur)
}
apply(filterManager, input, output) {
let renderTarget = filterManager.getRenderTarget(true)
this.tiltShiftXFilter.apply(filterManager, input, renderTarget)
this.tiltShiftYFilter.apply(filterManager, renderTarget, output)
filterManager.returnRenderTarget(renderTarget)
}
/**
* The strength of the blur.
*
* @member {number}
*/
get blur() {
return this.tiltShiftXFilter.blur
}
set blur(value) {
this.tiltShiftXFilter.blur = this.tiltShiftYFilter.blur = value
}
/**
* The blur shape.
*
* @member {PIXI.Rectangle|PIXI.Circle|PIXI.DisplayObject}
*/
get shape() {
return this.tiltShiftXFilter.shape
}
set shape(value) {
this.tiltShiftXFilter.shape = this.tiltShiftYFilter.shape = this.normalize(value)
}
/**
*
* @private
* @param {PIXI.Rectangle|PIXI.Circle|PIXI.DisplayObject} value
* @returns {Object}
*/
normalize(value) {
let shape = null
if (value instanceof PIXI.Circle) {
shape = { type: 'circle', x: value.x, y: value.y, r: value.radius }
} else if (value instanceof PIXI.Rectangle) {
shape = {
type: 'rectangle',
x: value.x,
y: value.y,
width: value.width,
height: value.height
}
} else {
const bounds = value.getBounds()
shape = {
type: 'rectangle',
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: bounds.height
}
}
return shape
}
}
/**
* A TiltShiftAxisFilter.
*
* @class
* @extends PIXI.Filter
* @abstract
* @private
*/
class TiltShiftAxisFilter extends PIXI.Filter {
constructor(shape, blur) {
const vertex = `
attribute vec2 aVertexPosition;
attribute vec2 aTextureCoord;
uniform mat3 projectionMatrix;
varying vec2 vTextureCoord;
void main(void) {
gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
vTextureCoord = aTextureCoord;
}
`
const fragment = `
varying vec2 vTextureCoord;
uniform vec4 filterArea;
uniform sampler2D uSampler;
uniform int shape;
uniform vec4 rectangle;
uniform vec3 circle;
uniform float blur;
uniform vec2 delta;
uniform vec2 texSize;
float random(vec3 scale, float seed) {
return fract(sin(dot(gl_FragCoord.xyz + seed, scale)) * 43758.5453 + seed);
}
void main(void) {
// textureCoord to pixelCoord
vec2 pixelCoord = vTextureCoord * filterArea.xy - vec2(4.0, 4.0); // FIXME: There's a shift of 4 * 4 pixels, don't know why...
bool inside = false;
if (shape == 1) {
inside = distance(pixelCoord, circle.xy) <= circle.z;
} else if (shape == 2) {
inside = pixelCoord.x >= rectangle.x && pixelCoord.x <= rectangle.z && pixelCoord.y >= rectangle.y && pixelCoord.y <= rectangle.w;
}
if (inside) {
vec4 color = vec4(0.0);
float total = 0.0;
float offset = random(vec3(12.9898, 78.233, 151.7182), 0.0);
for (float t = -30.0; t <= 30.0; t++) {
float percent = (t + offset - 0.5) / 30.0;
float weight = 1.0 - abs(percent);
vec4 sample = texture2D(uSampler, vTextureCoord + delta / texSize * percent * blur);
sample.rgb *= sample.a;
color += sample * weight;
total += weight;
}
gl_FragColor = color / total;
gl_FragColor.rgb /= gl_FragColor.a + 0.00001;
} else {
gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
}
}
`
super(vertex, fragment)
if (shape.type === 'circle') {
this.uniforms.shape = 1
this.uniforms.circle = [shape.x, shape.y, shape.r]
} else {
this.uniforms.shape = 2
this.uniforms.rectangle = [shape.x, shape.y, shape.x + shape.width, shape.y + shape.height]
}
this.uniforms.blur = blur
this.uniforms.delta = new PIXI.Point(0, 0)
this.uniforms.texSize = new PIXI.Point(480, 270)
this.updateDelta()
}
/**
* The strength of the blur.
*
* @member {number}
* @memberof PIXI.filters.TiltShiftAxisFilter#
*/
get blur() {
return this.uniforms.blur
}
set blur(value) {
this.uniforms.blur = value
}
/**
* The blur shape.
*
* @member {PIXI.Rectangle}
* @memberof PIXI.filters.TiltShiftAxisFilter#
*/
get shape() {
if (this.uniforms.shape === 1) {
const circle = this.uniforms.circle
return new PIXI.Circle(circle[0], circle[1], circle[2])
} else {
const rectangle = this.uniforms.rectangle
return new PIXI.Rectangle(rectangle[0], rectangle[1], rectangle[2], rectangle[3])
}
}
set shape(value) {
if (value.type === 'circle') {
this.uniforms.shape = 1
this.uniforms.circle = [value.x, value.y, value.r]
} else {
this.uniforms.shape = 2
this.uniforms.rectangle = [value.x, value.y, value.x + value.width, value.y + value.height]
}
}
}
/**
* A TiltShiftXFilter.
*
* @class
* @extends PIXI.TiltShiftAxisFilter
* @private
*/
class TiltShiftXFilter extends TiltShiftAxisFilter {
/**
* Updates the filter delta values.
*/
updateDelta() {
this.uniforms.delta.x = 0.1
this.uniforms.delta.y = 0
}
}
/**
* A TiltShiftYFilter.
*
* @class
* @extends PIXI.TiltShiftAxisFilter
* @private
*/
class TiltShiftYFilter extends TiltShiftAxisFilter {
/**
* Updates the filter delta values.
*/
updateDelta() {
this.uniforms.delta.x = 0
this.uniforms.delta.y = 0.1
}
}