Working implementation with replaceAll hack

pull/559/head
Mattia Iavarone 6 years ago
parent e4f9b6a209
commit 0f0c6dc91c
  1. 126
      cameraview/src/main/java/com/otaliastudios/cameraview/filter/MultiFilter.java
  2. 9
      demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java

@ -1,5 +1,6 @@
package com.otaliastudios.cameraview.filter;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import androidx.annotation.NonNull;
@ -7,20 +8,28 @@ import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.internal.GlUtils;
import com.otaliastudios.cameraview.size.Size;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@SuppressWarnings("unused")
public class MultiFilter implements Filter {
private final static int TARGET = GLES20.GL_TEXTURE_2D;
private static class State {
boolean isCreated = false;
int programHandle = -1;
int framebufferId = -1;
int textureId = -1;
Size size = null;
}
private final Map<Filter, State> filters = new HashMap<>();
private final List<Filter> filters = new ArrayList<>();
private final Map<Filter, State> states = new HashMap<>();
private final Object lock = new Object();
private Size size = null;
@ -40,14 +49,15 @@ public class MultiFilter implements Filter {
public void addFilter(@NonNull Filter filter) {
if (filter instanceof MultiFilter) {
MultiFilter multiFilter = (MultiFilter) filter;
for (Filter multiChild : multiFilter.filters.keySet()) {
for (Filter multiChild : multiFilter.filters) {
addFilter(multiChild);
}
return;
}
synchronized (lock) {
if (!filters.keySet().contains(filter)) {
filters.put(filter, new State());
if (!filters.contains(filter)) {
filters.add(filter);
states.put(filter, new State());
}
}
}
@ -56,31 +66,81 @@ public class MultiFilter implements Filter {
// with cleanup. Cleanup must happen on the GL thread so we'd have to wait
// for new rendering call (which might not even happen).
private void maybeCreate(@NonNull Filter filter) {
State state = filters.get(filter);
private void maybeCreate(@NonNull Filter filter, boolean isFirst) {
State state = states.get(filter);
//noinspection ConstantConditions
if (!state.isCreated) {
state.isCreated = true;
state.programHandle = GlUtils.createProgram(filter.getVertexShader(), filter.getFragmentShader());
String shader = filter.getFragmentShader();
if (!isFirst) {
// The first shader actually reads from a OES texture, but the others
// will read from the 2d framebuffer texture. This is a dirty hack.
shader = shader.replace("samplerExternalOES ", "sampler2D ");
}
state.programHandle = GlUtils.createProgram(filter.getVertexShader(), shader);
filter.onCreate(state.programHandle);
// TODO extra creation
}
}
private void maybeDestroy(@NonNull Filter filter) {
State state = filters.get(filter);
State state = states.get(filter);
//noinspection ConstantConditions
if (state.isCreated) {
state.isCreated = false;
filter.onDestroy();
GLES20.glDeleteProgram(state.programHandle);
state.programHandle = -1;
// TODO extra cleanup.
}
}
private void maybeCreateFramebuffer(@NonNull Filter filter, boolean isLast) {
if (isLast) return;
State state = states.get(filter);
//noinspection ConstantConditions
if (state.framebufferId == -1) {
int[] framebufferArray = new int[1];
int[] textureArray = new int[1];
GLES20.glGenFramebuffers(1, framebufferArray, 0);
GLES20.glGenTextures(1, textureArray, 0);
state.framebufferId = framebufferArray[0];
state.textureId = textureArray[0];
GLES20.glBindTexture(TARGET, state.textureId);
GLES20.glTexImage2D(TARGET, 0, GLES20.GL_RGBA, state.size.getWidth(), state.size.getHeight(), 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
GLES20.glTexParameterf(TARGET, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameterf(TARGET, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameterf(TARGET, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameterf(TARGET, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, state.framebufferId);
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER,
GLES20.GL_COLOR_ATTACHMENT0,
TARGET,
state.textureId,
0);
int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) {
throw new RuntimeException("Invalid framebuffer generation. Error:" + status);
}
GLES20.glBindTexture(TARGET, 0);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
}
}
private void maybeDestroyFramebuffer(@NonNull Filter filter) {
State state = states.get(filter);
//noinspection ConstantConditions
if (state.framebufferId != -1) {
GLES20.glDeleteFramebuffers(1, new int[]{state.framebufferId}, 0);
state.framebufferId = -1;
GLES20.glDeleteTextures(1, new int[]{state.textureId}, 0);
state.textureId = -1;
}
}
private void maybeSetSize(@NonNull Filter filter) {
State state = filters.get(filter);
State state = states.get(filter);
//noinspection ConstantConditions
if (size != null && !size.equals(state.size)) {
state.size = size;
@ -109,7 +169,8 @@ public class MultiFilter implements Filter {
@Override
public void onDestroy() {
synchronized (lock) {
for (Filter filter : filters.keySet()) {
for (Filter filter : filters) {
maybeDestroyFramebuffer(filter);
maybeDestroy(filter);
}
}
@ -119,7 +180,7 @@ public class MultiFilter implements Filter {
public void setSize(int width, int height) {
size = new Size(width, height);
synchronized (lock) {
for (Filter filter : filters.keySet()) {
for (Filter filter : filters) {
maybeSetSize(filter);
}
}
@ -128,12 +189,43 @@ public class MultiFilter implements Filter {
@Override
public void draw(float[] transformMatrix) {
synchronized (lock) {
for (Filter filter : filters.keySet()) {
maybeCreate(filter);
for (int i = 0; i < filters.size(); i++) {
boolean isFirst = i == 0;
boolean isLast = i == filters.size() - 1;
Filter filter = filters.get(i);
State state = states.get(filter);
maybeSetSize(filter);
maybeCreate(filter, isFirst);
maybeCreateFramebuffer(filter, isLast);
//noinspection ConstantConditions
GLES20.glUseProgram(state.programHandle);
// Define the output framebuffer.
// Each filter outputs into its own framebuffer object, except the
// last filter, which outputs into the default framebuffer.
if (!isLast) {
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, state.framebufferId);
GLES20.glClearColor(0, 0, 0, 0);
} else {
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
}
// Perform the actual drawing.
filter.draw(transformMatrix);
// Set the input for the next cycle:
// It is the framebuffer texture from this cycle. If this is the last
// filter, reset this value just to cleanup.
if (!isLast) {
GLES20.glBindTexture(TARGET, state.textureId);
} else {
GLES20.glBindTexture(TARGET, 0);
}
GLES20.glUseProgram(0);
}
}
// TODO
}
@NonNull
@ -141,7 +233,7 @@ public class MultiFilter implements Filter {
public Filter copy() {
synchronized (lock) {
MultiFilter copy = new MultiFilter();
for (Filter filter : filters.keySet()) {
for (Filter filter : filters) {
copy.addFilter(filter.copy());
}
return copy;

@ -5,6 +5,7 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.YuvImage;
@ -28,7 +29,9 @@ import com.otaliastudios.cameraview.controls.Mode;
import com.otaliastudios.cameraview.VideoResult;
import com.otaliastudios.cameraview.controls.Preview;
import com.otaliastudios.cameraview.filter.Filters;
import com.otaliastudios.cameraview.filter.MultiFilter;
import com.otaliastudios.cameraview.filters.BrightnessFilter;
import com.otaliastudios.cameraview.filters.DuotoneFilter;
import com.otaliastudios.cameraview.frame.Frame;
import com.otaliastudios.cameraview.frame.FrameProcessor;
@ -334,6 +337,12 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
Filters filter = mAllFilters[mCurrentFilter];
camera.setFilter(filter.newInstance());
message(filter.toString(), false);
// To test MultiFilter:
// DuotoneFilter duotone = new DuotoneFilter();
// duotone.setFirstColor(Color.RED);
// duotone.setSecondColor(Color.GREEN);
// camera.setFilter(new MultiFilter(filter.newInstance(), duotone));
}
@Override

Loading…
Cancel
Save