/******************************************************************************* * Copyright 2012 bmanuel * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.bitfire.postprocessing.filters; import com.badlogic.gdx.utils.IntMap; import com.bitfire.postprocessing.utils.PingPongBuffer; public final class Blur extends MultipassFilter { // @formatter:off private enum Tap { Tap3x3(1), Tap5x5(2), // Tap7x7( 3 ) ; public final int radius; private Tap (int radius) { this.radius = radius; } } public enum BlurType { Gaussian3x3(Tap.Tap3x3), Gaussian3x3b(Tap.Tap3x3), // R=5 (11x11, policy "higher-then-discard") Gaussian5x5(Tap.Tap5x5), Gaussian5x5b(Tap.Tap5x5), // R=9 (19x19, policy "higher-then-discard") ; public final Tap tap; private BlurType (Tap tap) { this.tap = tap; } } // @formatter:on // blur private BlurType type; private float amount; private int passes; // fbo, textures private float invWidth, invHeight; private final IntMap<Convolve2D> convolve = new IntMap<Convolve2D>(Tap.values().length); public Blur (int width, int height) { // precompute constants this.invWidth = 1f / (float)width; this.invHeight = 1f / (float)height; this.passes = 1; this.amount = 1f; // create filters for (Tap tap : Tap.values()) { convolve.put(tap.radius, new Convolve2D(tap.radius)); } setType(BlurType.Gaussian5x5); } public void dispose () { for (Convolve2D c : convolve.values()) { c.dispose(); } } public void setPasses (int passes) { this.passes = passes; } public void setType (BlurType type) { if (this.type != type) { this.type = type; computeBlurWeightings(); } } // not all blur types support custom amounts at this time public void setAmount (float amount) { this.amount = amount; computeBlurWeightings(); } public int getPasses () { return passes; } public BlurType getType () { return type; } // not all blur types support custom amounts at this time public float getAmount () { return amount; } @Override public void render (PingPongBuffer buffer) { Convolve2D c = convolve.get(this.type.tap.radius); for (int i = 0; i < this.passes; i++) { c.render(buffer); } } private void computeBlurWeightings () { boolean hasdata = true; Convolve2D c = convolve.get(this.type.tap.radius); float[] outWeights = c.weights; float[] outOffsetsH = c.offsetsHor; float[] outOffsetsV = c.offsetsVert; float dx = this.invWidth; float dy = this.invHeight; switch (this.type) { case Gaussian3x3: case Gaussian5x5: computeKernel(this.type.tap.radius, this.amount, outWeights); computeOffsets(this.type.tap.radius, this.invWidth, this.invHeight, outOffsetsH, outOffsetsV); break; case Gaussian3x3b: // weights and offsets are computed from a binomial distribution // and reduced to be used *only* with bilinearly-filtered texture lookups // // with radius = 1f // weights outWeights[0] = 0.352941f; outWeights[1] = 0.294118f; outWeights[2] = 0.352941f; // horizontal offsets outOffsetsH[0] = -1.33333f; outOffsetsH[1] = 0f; outOffsetsH[2] = 0f; outOffsetsH[3] = 0f; outOffsetsH[4] = 1.33333f; outOffsetsH[5] = 0f; // vertical offsets outOffsetsV[0] = 0f; outOffsetsV[1] = -1.33333f; outOffsetsV[2] = 0f; outOffsetsV[3] = 0f; outOffsetsV[4] = 0f; outOffsetsV[5] = 1.33333f; // scale offsets from binomial space to screen space for (int i = 0; i < c.length * 2; i++) { outOffsetsH[i] *= dx; outOffsetsV[i] *= dy; } break; case Gaussian5x5b: // weights and offsets are computed from a binomial distribution // and reduced to be used *only* with bilinearly-filtered texture lookups // // with radius = 2f // weights outWeights[0] = 0.0702703f; outWeights[1] = 0.316216f; outWeights[2] = 0.227027f; outWeights[3] = 0.316216f; outWeights[4] = 0.0702703f; // horizontal offsets outOffsetsH[0] = -3.23077f; outOffsetsH[1] = 0f; outOffsetsH[2] = -1.38462f; outOffsetsH[3] = 0f; outOffsetsH[4] = 0f; outOffsetsH[5] = 0f; outOffsetsH[6] = 1.38462f; outOffsetsH[7] = 0f; outOffsetsH[8] = 3.23077f; outOffsetsH[9] = 0f; // vertical offsets outOffsetsV[0] = 0f; outOffsetsV[1] = -3.23077f; outOffsetsV[2] = 0f; outOffsetsV[3] = -1.38462f; outOffsetsV[4] = 0f; outOffsetsV[5] = 0f; outOffsetsV[6] = 0f; outOffsetsV[7] = 1.38462f; outOffsetsV[8] = 0f; outOffsetsV[9] = 3.23077f; // scale offsets from binomial space to screen space for (int i = 0; i < c.length * 2; i++) { outOffsetsH[i] *= dx; outOffsetsV[i] *= dy; } break; default: hasdata = false; break; } if (hasdata) { c.upload(); } } private void computeKernel (int blurRadius, float blurAmount, float[] outKernel) { int radius = blurRadius; // float sigma = (float)radius / amount; float sigma = blurAmount; float twoSigmaSquare = 2.0f * sigma * sigma; float sigmaRoot = (float)Math.sqrt(twoSigmaSquare * Math.PI); float total = 0.0f; float distance = 0.0f; int index = 0; for (int i = -radius; i <= radius; ++i) { distance = i * i; index = i + radius; outKernel[index] = (float)Math.exp(-distance / twoSigmaSquare) / sigmaRoot; total += outKernel[index]; } int size = (radius * 2) + 1; for (int i = 0; i < size; ++i) { outKernel[i] /= total; } } private void computeOffsets (int blurRadius, float dx, float dy, float[] outOffsetH, float[] outOffsetV) { int radius = blurRadius; final int X = 0, Y = 1; for (int i = -radius, j = 0; i <= radius; ++i, j += 2) { outOffsetH[j + X] = i * dx; outOffsetH[j + Y] = 0; outOffsetV[j + X] = 0; outOffsetV[j + Y] = i * dy; } } @Override public void rebind () { computeBlurWeightings(); } }