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; package com.otaliastudios.cameraview.filter;
import android.opengl.GLES11Ext;
import android.opengl.GLES20; import android.opengl.GLES20;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -7,20 +8,28 @@ import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.internal.GlUtils; import com.otaliastudios.cameraview.internal.GlUtils;
import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.Size;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
@SuppressWarnings("unused")
public class MultiFilter implements Filter { public class MultiFilter implements Filter {
private final static int TARGET = GLES20.GL_TEXTURE_2D;
private static class State { private static class State {
boolean isCreated = false; boolean isCreated = false;
int programHandle = -1; int programHandle = -1;
int framebufferId = -1;
int textureId = -1;
Size size = null; 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 final Object lock = new Object();
private Size size = null; private Size size = null;
@ -40,14 +49,15 @@ public class MultiFilter implements Filter {
public void addFilter(@NonNull Filter filter) { public void addFilter(@NonNull Filter filter) {
if (filter instanceof MultiFilter) { if (filter instanceof MultiFilter) {
MultiFilter multiFilter = (MultiFilter) filter; MultiFilter multiFilter = (MultiFilter) filter;
for (Filter multiChild : multiFilter.filters.keySet()) { for (Filter multiChild : multiFilter.filters) {
addFilter(multiChild); addFilter(multiChild);
} }
return; return;
} }
synchronized (lock) { synchronized (lock) {
if (!filters.keySet().contains(filter)) { if (!filters.contains(filter)) {
filters.put(filter, new State()); 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 // with cleanup. Cleanup must happen on the GL thread so we'd have to wait
// for new rendering call (which might not even happen). // for new rendering call (which might not even happen).
private void maybeCreate(@NonNull Filter filter) { private void maybeCreate(@NonNull Filter filter, boolean isFirst) {
State state = filters.get(filter); State state = states.get(filter);
//noinspection ConstantConditions //noinspection ConstantConditions
if (!state.isCreated) { if (!state.isCreated) {
state.isCreated = true; 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); filter.onCreate(state.programHandle);
// TODO extra creation
} }
} }
private void maybeDestroy(@NonNull Filter filter) { private void maybeDestroy(@NonNull Filter filter) {
State state = filters.get(filter); State state = states.get(filter);
//noinspection ConstantConditions //noinspection ConstantConditions
if (state.isCreated) { if (state.isCreated) {
state.isCreated = false; state.isCreated = false;
filter.onDestroy(); filter.onDestroy();
GLES20.glDeleteProgram(state.programHandle); GLES20.glDeleteProgram(state.programHandle);
state.programHandle = -1; 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) { private void maybeSetSize(@NonNull Filter filter) {
State state = filters.get(filter); State state = states.get(filter);
//noinspection ConstantConditions //noinspection ConstantConditions
if (size != null && !size.equals(state.size)) { if (size != null && !size.equals(state.size)) {
state.size = size; state.size = size;
@ -109,7 +169,8 @@ public class MultiFilter implements Filter {
@Override @Override
public void onDestroy() { public void onDestroy() {
synchronized (lock) { synchronized (lock) {
for (Filter filter : filters.keySet()) { for (Filter filter : filters) {
maybeDestroyFramebuffer(filter);
maybeDestroy(filter); maybeDestroy(filter);
} }
} }
@ -119,7 +180,7 @@ public class MultiFilter implements Filter {
public void setSize(int width, int height) { public void setSize(int width, int height) {
size = new Size(width, height); size = new Size(width, height);
synchronized (lock) { synchronized (lock) {
for (Filter filter : filters.keySet()) { for (Filter filter : filters) {
maybeSetSize(filter); maybeSetSize(filter);
} }
} }
@ -128,12 +189,43 @@ public class MultiFilter implements Filter {
@Override @Override
public void draw(float[] transformMatrix) { public void draw(float[] transformMatrix) {
synchronized (lock) { synchronized (lock) {
for (Filter filter : filters.keySet()) { for (int i = 0; i < filters.size(); i++) {
maybeCreate(filter); boolean isFirst = i == 0;
boolean isLast = i == filters.size() - 1;
Filter filter = filters.get(i);
State state = states.get(filter);
maybeSetSize(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 @NonNull
@ -141,7 +233,7 @@ public class MultiFilter implements Filter {
public Filter copy() { public Filter copy() {
synchronized (lock) { synchronized (lock) {
MultiFilter copy = new MultiFilter(); MultiFilter copy = new MultiFilter();
for (Filter filter : filters.keySet()) { for (Filter filter : filters) {
copy.addFilter(filter.copy()); copy.addFilter(filter.copy());
} }
return copy; return copy;

@ -5,6 +5,7 @@ import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.ImageFormat; import android.graphics.ImageFormat;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.YuvImage; import android.graphics.YuvImage;
@ -28,7 +29,9 @@ import com.otaliastudios.cameraview.controls.Mode;
import com.otaliastudios.cameraview.VideoResult; import com.otaliastudios.cameraview.VideoResult;
import com.otaliastudios.cameraview.controls.Preview; import com.otaliastudios.cameraview.controls.Preview;
import com.otaliastudios.cameraview.filter.Filters; import com.otaliastudios.cameraview.filter.Filters;
import com.otaliastudios.cameraview.filter.MultiFilter;
import com.otaliastudios.cameraview.filters.BrightnessFilter; import com.otaliastudios.cameraview.filters.BrightnessFilter;
import com.otaliastudios.cameraview.filters.DuotoneFilter;
import com.otaliastudios.cameraview.frame.Frame; import com.otaliastudios.cameraview.frame.Frame;
import com.otaliastudios.cameraview.frame.FrameProcessor; import com.otaliastudios.cameraview.frame.FrameProcessor;
@ -334,6 +337,12 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
Filters filter = mAllFilters[mCurrentFilter]; Filters filter = mAllFilters[mCurrentFilter];
camera.setFilter(filter.newInstance()); camera.setFilter(filter.newInstance());
message(filter.toString(), false); 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 @Override

Loading…
Cancel
Save