Extension to Gaussian Blur: support changing blur over time
PiperOrigin-RevId: 597809380
This commit is contained in:
parent
59afb4fb01
commit
78c419e566
@ -99,4 +99,28 @@ public class GaussianBlurTest {
|
|||||||
assertThat(actualPresentationTimesUs).containsExactly(32_000L);
|
assertThat(actualPresentationTimesUs).containsExactly(32_000L);
|
||||||
getAndAssertOutputBitmaps(textureBitmapReader, actualPresentationTimesUs, testId, ASSET_PATH);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,11 +51,10 @@ public final class GaussianBlur extends SeparableConvolution {
|
|||||||
*/
|
*/
|
||||||
public GaussianBlur(float sigma) {
|
public GaussianBlur(float sigma) {
|
||||||
this(sigma, /* numStandardDeviations= */ 2.0f);
|
this(sigma, /* numStandardDeviations= */ 2.0f);
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ConvolutionFunction1D getConvolution() {
|
public ConvolutionFunction1D getConvolution(long presentationTimeUs) {
|
||||||
return new GaussianFunction(sigma, numStandardDeviations);
|
return new GaussianFunction(sigma, numStandardDeviations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,9 @@ import static java.lang.Math.exp;
|
|||||||
import static java.lang.Math.sqrt;
|
import static java.lang.Math.sqrt;
|
||||||
|
|
||||||
import androidx.annotation.FloatRange;
|
import androidx.annotation.FloatRange;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of a symmetric Gaussian function with a limited domain.
|
* Implementation of a symmetric Gaussian function with a limited domain.
|
||||||
@ -68,4 +70,21 @@ public final class GaussianFunction implements ConvolutionFunction1D {
|
|||||||
return (float)
|
return (float)
|
||||||
(exp(-samplePositionOverSigma * samplePositionOverSigma / 2) / sqrt(2 * PI) / sigma);
|
(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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,8 +46,12 @@ public abstract class SeparableConvolution implements GlEffect {
|
|||||||
this.scaleFactor = scaleFactor;
|
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
|
@Override
|
||||||
public GlShaderProgram toGlShaderProgram(Context context, boolean useHdr)
|
public GlShaderProgram toGlShaderProgram(Context context, boolean useHdr)
|
||||||
|
@ -34,6 +34,7 @@ import com.google.common.util.concurrent.MoreExecutors;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ShortBuffer;
|
import java.nio.ShortBuffer;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link GlShaderProgram} for performing separable convolutions.
|
* A {@link GlShaderProgram} for performing separable convolutions.
|
||||||
@ -87,6 +88,7 @@ import java.util.concurrent.Executor;
|
|||||||
private float functionLutCenterX;
|
private float functionLutCenterX;
|
||||||
private float functionLutDomainStart;
|
private float functionLutDomainStart;
|
||||||
private float functionLutWidth;
|
private float functionLutWidth;
|
||||||
|
private @MonotonicNonNull ConvolutionFunction1D lastConvolutionFunction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance.
|
* Creates an instance.
|
||||||
@ -108,11 +110,12 @@ import java.util.concurrent.Executor;
|
|||||||
inputListener = new InputListener() {};
|
inputListener = new InputListener() {};
|
||||||
outputListener = new OutputListener() {};
|
outputListener = new OutputListener() {};
|
||||||
errorListener = (frameProcessingException) -> {};
|
errorListener = (frameProcessingException) -> {};
|
||||||
|
|
||||||
errorListenerExecutor = MoreExecutors.directExecutor();
|
errorListenerExecutor = MoreExecutors.directExecutor();
|
||||||
lastInputSize = Size.ZERO;
|
lastInputSize = Size.ZERO;
|
||||||
intermediateSize = Size.ZERO;
|
intermediateSize = Size.ZERO;
|
||||||
outputSize = Size.ZERO;
|
outputSize = Size.ZERO;
|
||||||
|
lastConvolutionFunction = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH);
|
glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH);
|
||||||
sharpTransformGlProgram =
|
sharpTransformGlProgram =
|
||||||
@ -160,7 +163,7 @@ import java.util.concurrent.Executor;
|
|||||||
+ " first.");
|
+ " first.");
|
||||||
try {
|
try {
|
||||||
ensureTexturesAreConfigured(
|
ensureTexturesAreConfigured(
|
||||||
glObjectsProvider, new Size(inputTexture.width, inputTexture.height));
|
glObjectsProvider, new Size(inputTexture.width, inputTexture.height), presentationTimeUs);
|
||||||
outputTextureInUse = true;
|
outputTextureInUse = true;
|
||||||
renderHorizontal(inputTexture);
|
renderHorizontal(inputTexture);
|
||||||
renderVertical();
|
renderVertical();
|
||||||
@ -269,10 +272,15 @@ import java.util.concurrent.Executor;
|
|||||||
renderOnePass(intermediateTexture.texId, /* isHorizontal= */ false);
|
renderOnePass(intermediateTexture.texId, /* isHorizontal= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ensureTexturesAreConfigured(GlObjectsProvider glObjectsProvider, Size inputSize)
|
private void ensureTexturesAreConfigured(
|
||||||
|
GlObjectsProvider glObjectsProvider, Size inputSize, long presentationTimeUs)
|
||||||
throws GlUtil.GlException {
|
throws GlUtil.GlException {
|
||||||
// Always update the function texture, as it could change on each render cycle.
|
ConvolutionFunction1D currentConvolutionFunction =
|
||||||
updateFunctionTexture(glObjectsProvider);
|
convolution.getConvolution(presentationTimeUs);
|
||||||
|
if (!currentConvolutionFunction.equals(lastConvolutionFunction)) {
|
||||||
|
updateFunctionTexture(glObjectsProvider, currentConvolutionFunction);
|
||||||
|
lastConvolutionFunction = currentConvolutionFunction;
|
||||||
|
}
|
||||||
|
|
||||||
// Only update intermediate and output textures if the size changes.
|
// Only update intermediate and output textures if the size changes.
|
||||||
if (inputSize.equals(lastInputSize)) {
|
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
|
* Creates a function lookup table for the convolution, and stores it in a 16b floating point
|
||||||
* texture for GPU access.
|
* texture for GPU access.
|
||||||
*/
|
*/
|
||||||
private void updateFunctionTexture(GlObjectsProvider glObjectsProvider)
|
private void updateFunctionTexture(
|
||||||
|
GlObjectsProvider glObjectsProvider, ConvolutionFunction1D convolutionFunction)
|
||||||
throws GlUtil.GlException {
|
throws GlUtil.GlException {
|
||||||
|
|
||||||
ConvolutionFunction1D convolutionFunction = convolution.getConvolution();
|
|
||||||
|
|
||||||
int lutRasterSize =
|
int lutRasterSize =
|
||||||
(int)
|
(int)
|
||||||
Math.ceil(
|
Math.ceil(
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 8.4 KiB |
Loading…
x
Reference in New Issue
Block a user