Extension to Gaussian Blur: support changing blur over time

PiperOrigin-RevId: 597809380
This commit is contained in:
tofunmi 2024-01-12 04:50:24 -08:00 committed by Copybara-Service
parent 59afb4fb01
commit 78c419e566
6 changed files with 65 additions and 12 deletions

View File

@ -99,4 +99,28 @@ public class GaussianBlurTest {
assertThat(actualPresentationTimesUs).containsExactly(32_000L);
getAndAssertOutputBitmaps(textureBitmapReader, actualPresentationTimesUs, testId, ASSET_PATH);
}
@Test
@RequiresNonNull({"textureBitmapReader", "testId"})
public void gaussianBlur_sigmaChangesWithTime_differentFramesHaveDifferentBlurs()
throws Exception {
ImmutableList<Long> frameTimesUs = ImmutableList.of(32_000L, 71_000L);
ImmutableList<Long> actualPresentationTimesUs =
generateAndProcessFrames(
BLANK_FRAME_WIDTH,
BLANK_FRAME_HEIGHT,
frameTimesUs,
new SeparableConvolution() {
@Override
public ConvolutionFunction1D getConvolution(long presentationTimeUs) {
return new GaussianFunction(
presentationTimeUs < 40_000L ? 5f : 20f, /* numStandardDeviations= */ 2.0f);
}
},
textureBitmapReader,
TEXT_SPAN_CONSUMER);
assertThat(actualPresentationTimesUs).containsExactly(32_000L, 71_000L);
getAndAssertOutputBitmaps(textureBitmapReader, actualPresentationTimesUs, testId, ASSET_PATH);
}
}

View File

@ -51,11 +51,10 @@ public final class GaussianBlur extends SeparableConvolution {
*/
public GaussianBlur(float sigma) {
this(sigma, /* numStandardDeviations= */ 2.0f);
;
}
@Override
public ConvolutionFunction1D getConvolution() {
public ConvolutionFunction1D getConvolution(long presentationTimeUs) {
return new GaussianFunction(sigma, numStandardDeviations);
}
}

View File

@ -21,7 +21,9 @@ import static java.lang.Math.exp;
import static java.lang.Math.sqrt;
import androidx.annotation.FloatRange;
import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi;
import java.util.Objects;
/**
* Implementation of a symmetric Gaussian function with a limited domain.
@ -68,4 +70,21 @@ public final class GaussianFunction implements ConvolutionFunction1D {
return (float)
(exp(-samplePositionOverSigma * samplePositionOverSigma / 2) / sqrt(2 * PI) / sigma);
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (!(o instanceof GaussianFunction)) {
return false;
}
GaussianFunction that = (GaussianFunction) o;
return Float.compare(that.sigma, sigma) == 0 && Float.compare(that.numStdDev, numStdDev) == 0;
}
@Override
public int hashCode() {
return Objects.hash(sigma, numStdDev);
}
}

View File

@ -46,8 +46,12 @@ public abstract class SeparableConvolution implements GlEffect {
this.scaleFactor = scaleFactor;
}
/** Returns a {@linkplain ConvolutionFunction1D 1D convolution function}. */
public abstract ConvolutionFunction1D getConvolution();
/**
* Returns a {@linkplain ConvolutionFunction1D 1D convolution function}.
*
* @param presentationTimeUs The presentation timestamp of the input frame, in microseconds.
*/
public abstract ConvolutionFunction1D getConvolution(long presentationTimeUs);
@Override
public GlShaderProgram toGlShaderProgram(Context context, boolean useHdr)

View File

@ -34,6 +34,7 @@ import com.google.common.util.concurrent.MoreExecutors;
import java.io.IOException;
import java.nio.ShortBuffer;
import java.util.concurrent.Executor;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* A {@link GlShaderProgram} for performing separable convolutions.
@ -87,6 +88,7 @@ import java.util.concurrent.Executor;
private float functionLutCenterX;
private float functionLutDomainStart;
private float functionLutWidth;
private @MonotonicNonNull ConvolutionFunction1D lastConvolutionFunction;
/**
* Creates an instance.
@ -108,11 +110,12 @@ import java.util.concurrent.Executor;
inputListener = new InputListener() {};
outputListener = new OutputListener() {};
errorListener = (frameProcessingException) -> {};
errorListenerExecutor = MoreExecutors.directExecutor();
lastInputSize = Size.ZERO;
intermediateSize = Size.ZERO;
outputSize = Size.ZERO;
lastConvolutionFunction = null;
try {
glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH);
sharpTransformGlProgram =
@ -160,7 +163,7 @@ import java.util.concurrent.Executor;
+ " first.");
try {
ensureTexturesAreConfigured(
glObjectsProvider, new Size(inputTexture.width, inputTexture.height));
glObjectsProvider, new Size(inputTexture.width, inputTexture.height), presentationTimeUs);
outputTextureInUse = true;
renderHorizontal(inputTexture);
renderVertical();
@ -269,10 +272,15 @@ import java.util.concurrent.Executor;
renderOnePass(intermediateTexture.texId, /* isHorizontal= */ false);
}
private void ensureTexturesAreConfigured(GlObjectsProvider glObjectsProvider, Size inputSize)
private void ensureTexturesAreConfigured(
GlObjectsProvider glObjectsProvider, Size inputSize, long presentationTimeUs)
throws GlUtil.GlException {
// Always update the function texture, as it could change on each render cycle.
updateFunctionTexture(glObjectsProvider);
ConvolutionFunction1D currentConvolutionFunction =
convolution.getConvolution(presentationTimeUs);
if (!currentConvolutionFunction.equals(lastConvolutionFunction)) {
updateFunctionTexture(glObjectsProvider, currentConvolutionFunction);
lastConvolutionFunction = currentConvolutionFunction;
}
// Only update intermediate and output textures if the size changes.
if (inputSize.equals(lastInputSize)) {
@ -295,11 +303,10 @@ import java.util.concurrent.Executor;
* Creates a function lookup table for the convolution, and stores it in a 16b floating point
* texture for GPU access.
*/
private void updateFunctionTexture(GlObjectsProvider glObjectsProvider)
private void updateFunctionTexture(
GlObjectsProvider glObjectsProvider, ConvolutionFunction1D convolutionFunction)
throws GlUtil.GlException {
ConvolutionFunction1D convolutionFunction = convolution.getConvolution();
int lutRasterSize =
(int)
Math.ceil(

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB