* Egloo: create GlTextureDrawer

* Remove internal/utils

* Remove internal/utils (test)

* Remove internal/utils (unit tests)

* Remove EglCore usages

* Remove EglSurface usages

* Remove EglViewport usages

* Fix BaseFilter bug

* Add Texture.use

* Fix MultiFilter bug

* Use GlFramebuffer

* Use GlBindable.bind() in MultiFilter

* Use Egloo Jitpack (temp)

* BaseFilter changes

* Improve BaseFilter

* Use v0.4.0

* Fix test
pull/786/head
Mattia Iavarone 5 years ago committed by GitHub
parent 7f23c309ab
commit 968d181af4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      cameraview/build.gradle
  2. 36
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/BaseEglTest.java
  3. 3
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java
  4. 24
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/filter/BaseFilterTest.java
  5. 30
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/filter/MultiFilterTest.java
  6. 3
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/CamcorderProfilesTest.java
  7. 3
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/CropHelperTest.java
  8. 3
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/OrientationHelperTest.java
  9. 3
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/RotationHelperTest.java
  10. 3
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/WorkerHandlerTest.java
  11. 36
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/overlay/OverlayDrawerTest.java
  12. 3
      cameraview/src/main/AndroidManifest.xml
  13. 15
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java
  14. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraUtils.java
  15. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  16. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java
  17. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java
  18. 3
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java
  19. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/options/Camera1Options.java
  20. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/options/Camera2Options.java
  21. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/orchestrator/CameraOrchestrator.java
  22. 99
      cameraview/src/main/java/com/otaliastudios/cameraview/filter/BaseFilter.java
  23. 127
      cameraview/src/main/java/com/otaliastudios/cameraview/filter/MultiFilter.java
  24. 1
      cameraview/src/main/java/com/otaliastudios/cameraview/filter/SimpleFilter.java
  25. 6
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/AutoFixFilter.java
  26. 6
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/BrightnessFilter.java
  27. 6
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/ContrastFilter.java
  28. 10
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/DocumentaryFilter.java
  29. 10
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/DuotoneFilter.java
  30. 10
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/FillLightFilter.java
  31. 6
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/GammaFilter.java
  32. 14
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/GrainFilter.java
  33. 6
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/HueFilter.java
  34. 18
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/LomoishFilter.java
  35. 14
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/SaturationFilter.java
  36. 14
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/SharpnessFilter.java
  37. 6
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/TemperatureFilter.java
  38. 6
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/TintFilter.java
  39. 18
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/VignetteFilter.java
  40. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/CamcorderProfiles.java
  41. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/CropHelper.java
  42. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/ExifHelper.java
  43. 97
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/GlTextureDrawer.java
  44. 96
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/GlUtils.java
  45. 5
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/Issue514Workaround.java
  46. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/OrientationHelper.java
  47. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/Pool.java
  48. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/RotationHelper.java
  49. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/WorkerHandler.java
  50. 244
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglBaseSurface.java
  51. 364
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglCore.java
  52. 103
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglViewport.java
  53. 80
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglWindowSurface.java
  54. 27
      cameraview/src/main/java/com/otaliastudios/cameraview/overlay/OverlayDrawer.java
  55. 3
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/Full1PictureRecorder.java
  56. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/Full2PictureRecorder.java
  57. 6
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/Snapshot1PictureRecorder.java
  58. 52
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotGlPictureRecorder.java
  59. 51
      cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java
  60. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/video/Full1VideoRecorder.java
  61. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/video/Full2VideoRecorder.java
  62. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/video/FullVideoRecorder.java
  63. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/ByteBufferPool.java
  64. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/InputBufferPool.java
  65. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/MediaEncoder.java
  66. 3
      cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/MediaEncoderEngine.java
  67. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/OutputBufferPool.java
  68. 23
      cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/TextureMediaEncoder.java
  69. 4
      cameraview/src/test/java/com/otaliastudios/cameraview/internal/ExifHelperTest.java
  70. 4
      cameraview/src/test/java/com/otaliastudios/cameraview/internal/PoolTest.java
  71. 4
      demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java

@ -48,6 +48,7 @@ dependencies {
api 'androidx.lifecycle:lifecycle-common:2.1.0' api 'androidx.lifecycle:lifecycle-common:2.1.0'
api 'com.google.android.gms:play-services-tasks:17.0.0' api 'com.google.android.gms:play-services-tasks:17.0.0'
implementation 'androidx.annotation:annotation:1.1.0' implementation 'androidx.annotation:annotation:1.1.0'
implementation 'com.otaliastudios.opengl:egloo:0.4.0'
} }
//endregion //endregion

@ -1,32 +1,13 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview;
import android.opengl.EGL14;
import android.graphics.Canvas; import com.otaliastudios.opengl.core.EglCore;
import com.otaliastudios.opengl.surface.EglOffscreenSurface;
import com.otaliastudios.opengl.surface.EglSurface;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.otaliastudios.cameraview.internal.egl.EglBaseSurface;
import com.otaliastudios.cameraview.internal.egl.EglCore;
import com.otaliastudios.cameraview.internal.egl.EglViewport;
import com.otaliastudios.cameraview.overlay.Overlay;
import com.otaliastudios.cameraview.overlay.OverlayDrawer;
import com.otaliastudios.cameraview.size.Size;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
@ -36,19 +17,18 @@ public abstract class BaseEglTest extends BaseTest {
protected final static int HEIGHT = 100; protected final static int HEIGHT = 100;
protected EglCore eglCore; protected EglCore eglCore;
protected EglBaseSurface eglSurface; protected EglSurface eglSurface;
@Before @Before
public void setUp() { public void setUp() {
eglCore = new EglCore(null, EglCore.FLAG_RECORDABLE); eglCore = new EglCore(EGL14.EGL_NO_CONTEXT, EglCore.FLAG_RECORDABLE);
eglSurface = new EglBaseSurface(eglCore); eglSurface = new EglOffscreenSurface(eglCore, WIDTH, HEIGHT);
eglSurface.createOffscreenSurface(WIDTH, HEIGHT);
eglSurface.makeCurrent(); eglSurface.makeCurrent();
} }
@After @After
public void tearDown() { public void tearDown() {
eglSurface.releaseEglSurface(); eglSurface.release();
eglSurface = null; eglSurface = null;
eglCore.release(); eglCore.release();
eglCore = null; eglCore = null;

@ -31,7 +31,7 @@ import com.otaliastudios.cameraview.frame.FrameProcessor;
import com.otaliastudios.cameraview.size.SizeSelectors; import com.otaliastudios.cameraview.size.SizeSelectors;
import com.otaliastudios.cameraview.tools.Emulator; import com.otaliastudios.cameraview.tools.Emulator;
import com.otaliastudios.cameraview.tools.Op; import com.otaliastudios.cameraview.tools.Op;
import com.otaliastudios.cameraview.internal.utils.WorkerHandler; import com.otaliastudios.cameraview.internal.WorkerHandler;
import com.otaliastudios.cameraview.overlay.Overlay; import com.otaliastudios.cameraview.overlay.Overlay;
import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.Size;
import com.otaliastudios.cameraview.tools.Retry; import com.otaliastudios.cameraview.tools.Retry;
@ -45,7 +45,6 @@ import androidx.test.rule.GrantPermissionRule;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.mockito.ArgumentMatcher; import org.mockito.ArgumentMatcher;

@ -8,16 +8,16 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import com.otaliastudios.cameraview.BaseEglTest; import com.otaliastudios.cameraview.BaseEglTest;
import com.otaliastudios.cameraview.internal.GlUtils; import com.otaliastudios.cameraview.internal.GlTextureDrawer;
import com.otaliastudios.cameraview.internal.egl.EglViewport; import com.otaliastudios.opengl.program.GlTextureProgram;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
@ -91,11 +91,11 @@ public class BaseFilterTest extends BaseEglTest {
@Test @Test
public void testOnProgramCreate() { public void testOnProgramCreate() {
filter = new TestFilter(); filter = new TestFilter();
int handle = GlUtils.createProgram(filter.getVertexShader(), filter.getFragmentShader()); int handle = GlTextureProgram.create(filter.getVertexShader(), filter.getFragmentShader());
filter.onCreate(handle); filter.onCreate(handle);
assertTrue(filter.programHandle >= 0); assertNotNull(filter.program);
filter.onDestroy(); filter.onDestroy();
assertTrue(filter.programHandle < 0); assertNull(filter.program);
GLES20.glDeleteProgram(handle); GLES20.glDeleteProgram(handle);
} }
@ -111,18 +111,18 @@ public class BaseFilterTest extends BaseEglTest {
@Test @Test
public void testDraw() { public void testDraw() {
// Use an EglViewport which cares about GL setup. // Use a drawer which cares about GL setup.
filter = spy(new TestFilter()); filter = spy(new TestFilter());
EglViewport viewport = new EglViewport(filter); GlTextureDrawer drawer = new GlTextureDrawer();
int texture = viewport.createTexture(); drawer.setFilter(filter);
float[] matrix = new float[16]; float[] matrix = drawer.getTextureTransform();
viewport.drawFrame(0L, texture, matrix); drawer.draw(0L);
verify(filter, times(1)).onPreDraw(0L, matrix); verify(filter, times(1)).onPreDraw(0L, matrix);
verify(filter, times(1)).onDraw(0L); verify(filter, times(1)).onDraw(0L);
verify(filter, times(1)).onPostDraw(0L); verify(filter, times(1)).onPostDraw(0L);
viewport.release(); drawer.release();
} }
@Test(expected = RuntimeException.class) @Test(expected = RuntimeException.class)

@ -11,8 +11,8 @@ import com.otaliastudios.cameraview.filters.AutoFixFilter;
import com.otaliastudios.cameraview.filters.BrightnessFilter; import com.otaliastudios.cameraview.filters.BrightnessFilter;
import com.otaliastudios.cameraview.filters.DuotoneFilter; import com.otaliastudios.cameraview.filters.DuotoneFilter;
import com.otaliastudios.cameraview.filters.VignetteFilter; import com.otaliastudios.cameraview.filters.VignetteFilter;
import com.otaliastudios.cameraview.internal.GlUtils; import com.otaliastudios.cameraview.internal.GlTextureDrawer;
import com.otaliastudios.cameraview.internal.egl.EglViewport; import com.otaliastudios.opengl.program.GlProgram;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -127,7 +127,7 @@ public class MultiFilterTest extends BaseEglTest {
DuotoneFilter filter = spy(new DuotoneFilter()); DuotoneFilter filter = spy(new DuotoneFilter());
MultiFilter multiFilter = new MultiFilter(filter); MultiFilter multiFilter = new MultiFilter(filter);
int program = GlUtils.createProgram(multiFilter.getVertexShader(), int program = GlProgram.create(multiFilter.getVertexShader(),
multiFilter.getFragmentShader()); multiFilter.getFragmentShader());
multiFilter.onCreate(program); multiFilter.onCreate(program);
verify(filter, never()).onCreate(anyInt()); verify(filter, never()).onCreate(anyInt());
@ -142,11 +142,11 @@ public class MultiFilterTest extends BaseEglTest {
DuotoneFilter filter = spy(new DuotoneFilter()); DuotoneFilter filter = spy(new DuotoneFilter());
MultiFilter multiFilter = new MultiFilter(filter); MultiFilter multiFilter = new MultiFilter(filter);
multiFilter.setSize(WIDTH, HEIGHT); multiFilter.setSize(WIDTH, HEIGHT);
EglViewport viewport = new EglViewport(multiFilter); GlTextureDrawer drawer = new GlTextureDrawer();
int texture = viewport.createTexture(); drawer.setFilter(multiFilter);
float[] matrix = new float[16]; float[] matrix = drawer.getTextureTransform();
viewport.drawFrame(0L, texture, matrix); drawer.draw(0L);
viewport.release(); drawer.release();
// The child should have experienced the whole lifecycle. // The child should have experienced the whole lifecycle.
verify(filter, atLeastOnce()).getVertexShader(); verify(filter, atLeastOnce()).getVertexShader();
@ -165,7 +165,9 @@ public class MultiFilterTest extends BaseEglTest {
final DuotoneFilter filter2 = spy(new DuotoneFilter()); final DuotoneFilter filter2 = spy(new DuotoneFilter());
final MultiFilter multiFilter = new MultiFilter(filter1, filter2); final MultiFilter multiFilter = new MultiFilter(filter1, filter2);
multiFilter.setSize(WIDTH, HEIGHT); multiFilter.setSize(WIDTH, HEIGHT);
float[] matrix = new float[16]; GlTextureDrawer drawer = new GlTextureDrawer();
drawer.setFilter(multiFilter);
float[] matrix = drawer.getTextureTransform();
final int[] result = new int[1]; final int[] result = new int[1];
doAnswer(new Answer() { doAnswer(new Answer() {
@ -173,7 +175,7 @@ public class MultiFilterTest extends BaseEglTest {
public Object answer(InvocationOnMock invocation) { public Object answer(InvocationOnMock invocation) {
MultiFilter.State state = multiFilter.states.get(filter1); MultiFilter.State state = multiFilter.states.get(filter1);
assertNotNull(state); assertNotNull(state);
assertTrue(state.isCreated); assertTrue(state.isProgramCreated);
assertTrue(state.isFramebufferCreated); assertTrue(state.isFramebufferCreated);
GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, result, 0); GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, result, 0);
@ -189,7 +191,7 @@ public class MultiFilterTest extends BaseEglTest {
// The last filter has no FBO / texture. // The last filter has no FBO / texture.
MultiFilter.State state = multiFilter.states.get(filter2); MultiFilter.State state = multiFilter.states.get(filter2);
assertNotNull(state); assertNotNull(state);
assertTrue(state.isCreated); assertTrue(state.isProgramCreated);
assertFalse(state.isFramebufferCreated); assertFalse(state.isFramebufferCreated);
GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, result, 0); GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, result, 0);
@ -199,10 +201,8 @@ public class MultiFilterTest extends BaseEglTest {
} }
}).when(filter2).draw(eq(0L), any(float[].class)); }).when(filter2).draw(eq(0L), any(float[].class));
EglViewport viewport = new EglViewport(multiFilter); drawer.draw(0L);
int texture = viewport.createTexture(); drawer.release();
viewport.drawFrame(0L, texture, matrix);
viewport.release();
// Verify that both are drawn. // Verify that both are drawn.
verify(filter1, times(1)).draw(0L, matrix); verify(filter1, times(1)).draw(0L, matrix);

@ -1,4 +1,4 @@
package com.otaliastudios.cameraview.internal.utils; package com.otaliastudios.cameraview.internal;
import android.media.CamcorderProfile; import android.media.CamcorderProfile;
@ -8,6 +8,7 @@ import androidx.test.filters.SmallTest;
import com.otaliastudios.cameraview.BaseTest; import com.otaliastudios.cameraview.BaseTest;
import com.otaliastudios.cameraview.CameraUtils; import com.otaliastudios.cameraview.CameraUtils;
import com.otaliastudios.cameraview.internal.CamcorderProfiles;
import com.otaliastudios.cameraview.tools.SdkExclude; import com.otaliastudios.cameraview.tools.SdkExclude;
import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.Size;

@ -1,9 +1,10 @@
package com.otaliastudios.cameraview.internal.utils; package com.otaliastudios.cameraview.internal;
import android.graphics.Rect; import android.graphics.Rect;
import com.otaliastudios.cameraview.BaseTest; import com.otaliastudios.cameraview.BaseTest;
import com.otaliastudios.cameraview.internal.CropHelper;
import com.otaliastudios.cameraview.size.AspectRatio; import com.otaliastudios.cameraview.size.AspectRatio;
import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.Size;

@ -1,4 +1,4 @@
package com.otaliastudios.cameraview.internal.utils; package com.otaliastudios.cameraview.internal;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
@ -6,6 +6,7 @@ import androidx.test.filters.SmallTest;
import android.view.OrientationEventListener; import android.view.OrientationEventListener;
import com.otaliastudios.cameraview.BaseTest; import com.otaliastudios.cameraview.BaseTest;
import com.otaliastudios.cameraview.internal.OrientationHelper;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;

@ -1,4 +1,4 @@
package com.otaliastudios.cameraview.internal.utils; package com.otaliastudios.cameraview.internal;
import android.graphics.ImageFormat; import android.graphics.ImageFormat;
@ -8,6 +8,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import com.otaliastudios.cameraview.BaseTest; import com.otaliastudios.cameraview.BaseTest;
import com.otaliastudios.cameraview.internal.RotationHelper;
import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.Size;
import org.junit.Test; import org.junit.Test;

@ -1,9 +1,10 @@
package com.otaliastudios.cameraview.internal.utils; package com.otaliastudios.cameraview.internal;
import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.Tasks; import com.google.android.gms.tasks.Tasks;
import com.otaliastudios.cameraview.BaseTest; import com.otaliastudios.cameraview.BaseTest;
import com.otaliastudios.cameraview.internal.WorkerHandler;
import com.otaliastudios.cameraview.tools.Op; import com.otaliastudios.cameraview.tools.Op;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;

@ -1,48 +1,29 @@
package com.otaliastudios.cameraview.overlay; package com.otaliastudios.cameraview.overlay;
import android.content.res.XmlResourceParser;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Xml;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import com.otaliastudios.cameraview.BaseEglTest; import com.otaliastudios.cameraview.BaseEglTest;
import com.otaliastudios.cameraview.BaseTest; import com.otaliastudios.cameraview.internal.GlTextureDrawer;
import com.otaliastudios.cameraview.internal.egl.EglBaseSurface;
import com.otaliastudios.cameraview.internal.egl.EglCore;
import com.otaliastudios.cameraview.internal.egl.EglViewport;
import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.Size;
import org.hamcrest.BaseMatcher; import org.hamcrest.BaseMatcher;
import org.hamcrest.Description; import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.junit.After;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mockito;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@SmallTest @SmallTest
@ -79,22 +60,19 @@ public class OverlayDrawerTest extends BaseEglTest {
@Test @Test
public void testRender() { public void testRender() {
OverlayDrawer drawer = new OverlayDrawer(mock(Overlay.class), new Size(WIDTH, HEIGHT)); OverlayDrawer drawer = new OverlayDrawer(mock(Overlay.class), new Size(WIDTH, HEIGHT));
drawer.mViewport = spy(drawer.mViewport); drawer.mTextureDrawer = spy(drawer.mTextureDrawer);
drawer.draw(Overlay.Target.PICTURE_SNAPSHOT); drawer.draw(Overlay.Target.PICTURE_SNAPSHOT);
drawer.render(0L); drawer.render(0L);
verify(drawer.mViewport, times(1)).drawFrame( verify(drawer.mTextureDrawer, times(1)).draw(0L);
0L,
drawer.mTextureId,
drawer.getTransform()
);
} }
@Test @Test
public void testRelease() { public void testRelease() {
OverlayDrawer drawer = new OverlayDrawer(mock(Overlay.class), new Size(WIDTH, HEIGHT)); OverlayDrawer drawer = new OverlayDrawer(mock(Overlay.class), new Size(WIDTH, HEIGHT));
EglViewport viewport = spy(drawer.mViewport); GlTextureDrawer textureDrawer = spy(drawer.mTextureDrawer);
drawer.mViewport = viewport; drawer.mTextureDrawer = textureDrawer;
drawer.release(); drawer.release();
verify(viewport, times(1)).release(); verify(textureDrawer, times(1)).release();
} }
} }

@ -1,4 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.otaliastudios.cameraview"> package="com.otaliastudios.cameraview">
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
@ -20,6 +21,8 @@
android:name="android.hardware.microphone" android:name="android.hardware.microphone"
android:required="false"/> android:required="false"/>
<uses-sdk tools:overrideLibrary="com.otaliastudios.opengl" />
<application/> <application/>
</manifest> </manifest>

@ -2,16 +2,6 @@ package com.otaliastudios.cameraview;
import android.graphics.ImageFormat; import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.os.Build;
import android.util.Range;
import android.util.Rational;
import com.otaliastudios.cameraview.controls.Audio; import com.otaliastudios.cameraview.controls.Audio;
import com.otaliastudios.cameraview.controls.Control; import com.otaliastudios.cameraview.controls.Control;
@ -20,26 +10,21 @@ import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.controls.Flash; import com.otaliastudios.cameraview.controls.Flash;
import com.otaliastudios.cameraview.controls.PictureFormat; import com.otaliastudios.cameraview.controls.PictureFormat;
import com.otaliastudios.cameraview.controls.Preview; import com.otaliastudios.cameraview.controls.Preview;
import com.otaliastudios.cameraview.engine.mappers.Camera1Mapper;
import com.otaliastudios.cameraview.engine.mappers.Camera2Mapper;
import com.otaliastudios.cameraview.gesture.GestureAction; import com.otaliastudios.cameraview.gesture.GestureAction;
import com.otaliastudios.cameraview.controls.Grid; import com.otaliastudios.cameraview.controls.Grid;
import com.otaliastudios.cameraview.controls.Hdr; import com.otaliastudios.cameraview.controls.Hdr;
import com.otaliastudios.cameraview.controls.Mode; import com.otaliastudios.cameraview.controls.Mode;
import com.otaliastudios.cameraview.controls.VideoCodec; import com.otaliastudios.cameraview.controls.VideoCodec;
import com.otaliastudios.cameraview.controls.WhiteBalance; import com.otaliastudios.cameraview.controls.WhiteBalance;
import com.otaliastudios.cameraview.internal.utils.CamcorderProfiles;
import com.otaliastudios.cameraview.size.AspectRatio; import com.otaliastudios.cameraview.size.AspectRatio;
import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.Size;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
/** /**

@ -11,8 +11,8 @@ import android.os.Handler;
import com.otaliastudios.cameraview.controls.Facing; import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.engine.mappers.Camera1Mapper; import com.otaliastudios.cameraview.engine.mappers.Camera1Mapper;
import com.otaliastudios.cameraview.internal.utils.ExifHelper; import com.otaliastudios.cameraview.internal.ExifHelper;
import com.otaliastudios.cameraview.internal.utils.WorkerHandler; import com.otaliastudios.cameraview.internal.WorkerHandler;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;

@ -66,8 +66,8 @@ import com.otaliastudios.cameraview.gesture.PinchGestureFinder;
import com.otaliastudios.cameraview.gesture.ScrollGestureFinder; import com.otaliastudios.cameraview.gesture.ScrollGestureFinder;
import com.otaliastudios.cameraview.gesture.TapGestureFinder; import com.otaliastudios.cameraview.gesture.TapGestureFinder;
import com.otaliastudios.cameraview.internal.GridLinesLayout; import com.otaliastudios.cameraview.internal.GridLinesLayout;
import com.otaliastudios.cameraview.internal.utils.CropHelper; import com.otaliastudios.cameraview.internal.CropHelper;
import com.otaliastudios.cameraview.internal.utils.OrientationHelper; import com.otaliastudios.cameraview.internal.OrientationHelper;
import com.otaliastudios.cameraview.markers.AutoFocusMarker; import com.otaliastudios.cameraview.markers.AutoFocusMarker;
import com.otaliastudios.cameraview.markers.AutoFocusTrigger; import com.otaliastudios.cameraview.markers.AutoFocusTrigger;
import com.otaliastudios.cameraview.markers.MarkerLayout; import com.otaliastudios.cameraview.markers.MarkerLayout;

@ -37,7 +37,7 @@ import com.otaliastudios.cameraview.gesture.Gesture;
import com.otaliastudios.cameraview.controls.Hdr; import com.otaliastudios.cameraview.controls.Hdr;
import com.otaliastudios.cameraview.controls.Mode; import com.otaliastudios.cameraview.controls.Mode;
import com.otaliastudios.cameraview.controls.WhiteBalance; import com.otaliastudios.cameraview.controls.WhiteBalance;
import com.otaliastudios.cameraview.internal.utils.CropHelper; import com.otaliastudios.cameraview.internal.CropHelper;
import com.otaliastudios.cameraview.metering.MeteringRegions; import com.otaliastudios.cameraview.metering.MeteringRegions;
import com.otaliastudios.cameraview.metering.MeteringTransform; import com.otaliastudios.cameraview.metering.MeteringTransform;
import com.otaliastudios.cameraview.picture.Full1PictureRecorder; import com.otaliastudios.cameraview.picture.Full1PictureRecorder;

@ -60,7 +60,7 @@ import com.otaliastudios.cameraview.frame.Frame;
import com.otaliastudios.cameraview.frame.FrameManager; import com.otaliastudios.cameraview.frame.FrameManager;
import com.otaliastudios.cameraview.frame.ImageFrameManager; import com.otaliastudios.cameraview.frame.ImageFrameManager;
import com.otaliastudios.cameraview.gesture.Gesture; import com.otaliastudios.cameraview.gesture.Gesture;
import com.otaliastudios.cameraview.internal.utils.CropHelper; import com.otaliastudios.cameraview.internal.CropHelper;
import com.otaliastudios.cameraview.metering.MeteringRegions; import com.otaliastudios.cameraview.metering.MeteringRegions;
import com.otaliastudios.cameraview.picture.Full2PictureRecorder; import com.otaliastudios.cameraview.picture.Full2PictureRecorder;
import com.otaliastudios.cameraview.picture.Snapshot2PictureRecorder; import com.otaliastudios.cameraview.picture.Snapshot2PictureRecorder;

@ -2,7 +2,6 @@ package com.otaliastudios.cameraview.engine;
import android.content.Context; import android.content.Context;
import android.graphics.PointF; import android.graphics.PointF;
import android.graphics.RectF;
import android.location.Location; import android.location.Location;
@ -29,7 +28,7 @@ import com.otaliastudios.cameraview.engine.offset.Angles;
import com.otaliastudios.cameraview.engine.offset.Reference; import com.otaliastudios.cameraview.engine.offset.Reference;
import com.otaliastudios.cameraview.frame.Frame; import com.otaliastudios.cameraview.frame.Frame;
import com.otaliastudios.cameraview.frame.FrameManager; import com.otaliastudios.cameraview.frame.FrameManager;
import com.otaliastudios.cameraview.internal.utils.WorkerHandler; import com.otaliastudios.cameraview.internal.WorkerHandler;
import com.otaliastudios.cameraview.picture.PictureRecorder; import com.otaliastudios.cameraview.picture.PictureRecorder;
import com.otaliastudios.cameraview.preview.CameraPreview; import com.otaliastudios.cameraview.preview.CameraPreview;
import com.otaliastudios.cameraview.controls.Audio; import com.otaliastudios.cameraview.controls.Audio;

@ -13,7 +13,7 @@ import com.otaliastudios.cameraview.controls.Hdr;
import com.otaliastudios.cameraview.controls.PictureFormat; import com.otaliastudios.cameraview.controls.PictureFormat;
import com.otaliastudios.cameraview.controls.WhiteBalance; import com.otaliastudios.cameraview.controls.WhiteBalance;
import com.otaliastudios.cameraview.engine.mappers.Camera1Mapper; import com.otaliastudios.cameraview.engine.mappers.Camera1Mapper;
import com.otaliastudios.cameraview.internal.utils.CamcorderProfiles; import com.otaliastudios.cameraview.internal.CamcorderProfiles;
import com.otaliastudios.cameraview.size.AspectRatio; import com.otaliastudios.cameraview.size.AspectRatio;
import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.Size;

@ -21,7 +21,7 @@ import com.otaliastudios.cameraview.controls.Hdr;
import com.otaliastudios.cameraview.controls.PictureFormat; import com.otaliastudios.cameraview.controls.PictureFormat;
import com.otaliastudios.cameraview.controls.WhiteBalance; import com.otaliastudios.cameraview.controls.WhiteBalance;
import com.otaliastudios.cameraview.engine.mappers.Camera2Mapper; import com.otaliastudios.cameraview.engine.mappers.Camera2Mapper;
import com.otaliastudios.cameraview.internal.utils.CamcorderProfiles; import com.otaliastudios.cameraview.internal.CamcorderProfiles;
import com.otaliastudios.cameraview.size.AspectRatio; import com.otaliastudios.cameraview.size.AspectRatio;
import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.Size;

@ -8,7 +8,7 @@ import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.TaskCompletionSource; import com.google.android.gms.tasks.TaskCompletionSource;
import com.google.android.gms.tasks.Tasks; import com.google.android.gms.tasks.Tasks;
import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.internal.utils.WorkerHandler; import com.otaliastudios.cameraview.internal.WorkerHandler;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;

@ -1,15 +1,13 @@
package com.otaliastudios.cameraview.filter; package com.otaliastudios.cameraview.filter;
import android.opengl.GLES20;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.internal.GlUtils;
import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.Size;
import com.otaliastudios.opengl.draw.GlDrawable;
import java.nio.FloatBuffer; import com.otaliastudios.opengl.draw.GlRect;
import com.otaliastudios.opengl.program.GlTextureProgram;
/** /**
* A base implementation of {@link Filter} that just leaves the fragment shader to subclasses. * A base implementation of {@link Filter} that just leaves the fragment shader to subclasses.
@ -90,26 +88,8 @@ public abstract class BaseFilter implements Filter {
+ "}\n"; + "}\n";
} }
// When the model/view/projection matrix is identity, this will exactly cover the viewport. @VisibleForTesting GlTextureProgram program = null;
private FloatBuffer vertexPosition = GlUtils.floatBuffer(new float[]{ private GlDrawable programDrawable = null;
-1.0f, -1.0f, // 0 bottom left
1.0f, -1.0f, // 1 bottom right
-1.0f, 1.0f, // 2 top left
1.0f, 1.0f, // 3 top right
});
private FloatBuffer textureCoordinates = GlUtils.floatBuffer(new float[]{
0.0f, 0.0f, // 0 bottom left
1.0f, 0.0f, // 1 bottom right
0.0f, 1.0f, // 2 top left
1.0f, 1.0f // 3 top right
});
private int vertexModelViewProjectionMatrixLocation = -1;
private int vertexTransformMatrixLocation = -1;
private int vertexPositionLocation = -1;
private int vertexTextureCoordinateLocation = -1;
@VisibleForTesting int programHandle = -1;
@VisibleForTesting Size size; @VisibleForTesting Size size;
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
@ -141,28 +121,23 @@ public abstract class BaseFilter implements Filter {
@Override @Override
public void onCreate(int programHandle) { public void onCreate(int programHandle) {
this.programHandle = programHandle; program = new GlTextureProgram(programHandle,
vertexPositionLocation = GLES20.glGetAttribLocation(programHandle, vertexPositionName); vertexPositionName,
GlUtils.checkLocation(vertexPositionLocation, vertexPositionName); vertexModelViewProjectionMatrixName,
vertexTextureCoordinateLocation = GLES20.glGetAttribLocation(programHandle, vertexTextureCoordinateName,
vertexTextureCoordinateName);
GlUtils.checkLocation(vertexTextureCoordinateLocation, vertexTextureCoordinateName);
vertexModelViewProjectionMatrixLocation = GLES20.glGetUniformLocation(programHandle,
vertexModelViewProjectionMatrixName);
GlUtils.checkLocation(vertexModelViewProjectionMatrixLocation,
vertexModelViewProjectionMatrixName);
vertexTransformMatrixLocation = GLES20.glGetUniformLocation(programHandle,
vertexTransformMatrixName); vertexTransformMatrixName);
GlUtils.checkLocation(vertexTransformMatrixLocation, vertexTransformMatrixName); programDrawable = new GlRect();
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
programHandle = -1; // Since we used the handle constructor of GlTextureProgram, calling release here
vertexPositionLocation = -1; // will NOT destroy the GL program. This is important because Filters are not supposed
vertexTextureCoordinateLocation = -1; // to have ownership of programs. Creation and deletion happen outside, and deleting twice
vertexModelViewProjectionMatrixLocation = -1; // would cause an error.
vertexTransformMatrixLocation = -1; program.release();
program = null;
programDrawable = null;
} }
@NonNull @NonNull
@ -178,7 +153,7 @@ public abstract class BaseFilter implements Filter {
@Override @Override
public void draw(long timestampUs, @NonNull float[] transformMatrix) { public void draw(long timestampUs, @NonNull float[] transformMatrix) {
if (programHandle == -1) { if (program == null) {
LOG.w("Filter.draw() called after destroying the filter. " + LOG.w("Filter.draw() called after destroying the filter. " +
"This can happen rarely because of threading."); "This can happen rarely because of threading.");
} else { } else {
@ -189,43 +164,18 @@ public abstract class BaseFilter implements Filter {
} }
protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) {
// Copy the model / view / projection matrix over. program.setTextureTransform(transformMatrix);
GLES20.glUniformMatrix4fv(vertexModelViewProjectionMatrixLocation, 1, program.onPreDraw(programDrawable, programDrawable.getModelMatrix());
false, GlUtils.IDENTITY_MATRIX, 0);
GlUtils.checkError("glUniformMatrix4fv");
// Copy the texture transformation matrix over.
GLES20.glUniformMatrix4fv(vertexTransformMatrixLocation, 1,
false, transformMatrix, 0);
GlUtils.checkError("glUniformMatrix4fv");
// Enable the "aPosition" vertex attribute.
// Connect vertexBuffer to "aPosition".
GLES20.glEnableVertexAttribArray(vertexPositionLocation);
GlUtils.checkError("glEnableVertexAttribArray: " + vertexPositionLocation);
GLES20.glVertexAttribPointer(vertexPositionLocation, 2, GLES20.GL_FLOAT,
false, 8, vertexPosition);
GlUtils.checkError("glVertexAttribPointer");
// Enable the "aTextureCoord" vertex attribute.
// Connect texBuffer to "aTextureCoord".
GLES20.glEnableVertexAttribArray(vertexTextureCoordinateLocation);
GlUtils.checkError("glEnableVertexAttribArray");
GLES20.glVertexAttribPointer(vertexTextureCoordinateLocation, 2, GLES20.GL_FLOAT,
false, 8, textureCoordinates);
GlUtils.checkError("glVertexAttribPointer");
} }
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
protected void onDraw(long timestampUs) { protected void onDraw(@SuppressWarnings("unused") long timestampUs) {
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); program.onDraw(programDrawable);
GlUtils.checkError("glDrawArrays");
} }
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
protected void onPostDraw(long timestampUs) { protected void onPostDraw(@SuppressWarnings("unused") long timestampUs) {
GLES20.glDisableVertexAttribArray(vertexPositionLocation); program.onPostDraw(programDrawable);
GLES20.glDisableVertexAttribArray(vertexTextureCoordinateLocation);
} }
@NonNull @NonNull
@ -244,6 +194,7 @@ public abstract class BaseFilter implements Filter {
return copy; return copy;
} }
@NonNull
protected BaseFilter onCopy() { protected BaseFilter onCopy() {
try { try {
return getClass().newInstance(); return getClass().newInstance();

@ -6,8 +6,12 @@ import android.opengl.GLES20;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.otaliastudios.cameraview.internal.GlUtils;
import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.Size;
import com.otaliastudios.opengl.core.Egloo;
import com.otaliastudios.opengl.program.GlProgram;
import com.otaliastudios.opengl.program.GlTextureProgram;
import com.otaliastudios.opengl.texture.GlFramebuffer;
import com.otaliastudios.opengl.texture.GlTexture;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -42,13 +46,12 @@ public class MultiFilter implements Filter, OneParameterFilter, TwoParameterFilt
@VisibleForTesting @VisibleForTesting
static class State { static class State {
@VisibleForTesting boolean isCreated = false; @VisibleForTesting boolean isProgramCreated = false;
@VisibleForTesting boolean isFramebufferCreated = false; @VisibleForTesting boolean isFramebufferCreated = false;
@VisibleForTesting Size size = null; @VisibleForTesting Size size = null;
private int programHandle = -1; private int programHandle = -1;
private int framebufferId = -1; private GlFramebuffer outputFramebuffer = null;
private int textureId = -1; private GlTexture outputTexture = null;
} }
@VisibleForTesting final List<Filter> filters = new ArrayList<>(); @VisibleForTesting final List<Filter> filters = new ArrayList<>();
@ -62,7 +65,6 @@ public class MultiFilter implements Filter, OneParameterFilter, TwoParameterFilt
* Creates a new group with the given filters. * Creates a new group with the given filters.
* @param filters children * @param filters children
*/ */
@SuppressWarnings("WeakerAccess")
public MultiFilter(@NonNull Filter... filters) { public MultiFilter(@NonNull Filter... filters) {
this(Arrays.asList(filters)); this(Arrays.asList(filters));
} }
@ -105,86 +107,54 @@ public class MultiFilter implements Filter, OneParameterFilter, TwoParameterFilt
// 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, boolean isFirst) { private void maybeCreateProgram(@NonNull Filter filter, boolean isFirst, boolean isLast) {
State state = states.get(filter); State state = states.get(filter);
//noinspection ConstantConditions //noinspection ConstantConditions
if (!state.isCreated) { if (state.isProgramCreated) return;
state.isCreated = true; state.isProgramCreated = true;
String shader = filter.getFragmentShader();
if (!isFirst) {
// The first shader actually reads from a OES texture, but the others // The first shader actually reads from a OES texture, but the others
// will read from the 2d framebuffer texture. This is a dirty hack. // will read from the 2d framebuffer texture. This is a dirty hack.
shader = shader.replace("samplerExternalOES ", "sampler2D "); String fragmentShader = isFirst
} ? filter.getFragmentShader()
state.programHandle = GlUtils.createProgram(filter.getVertexShader(), shader); : filter.getFragmentShader().replace("samplerExternalOES ", "sampler2D ");
String vertexShader = filter.getVertexShader();
state.programHandle = GlProgram.create(vertexShader, fragmentShader);
filter.onCreate(state.programHandle); filter.onCreate(state.programHandle);
} }
}
private void maybeDestroy(@NonNull Filter filter) { private void maybeDestroyProgram(@NonNull Filter filter) {
State state = states.get(filter); State state = states.get(filter);
//noinspection ConstantConditions //noinspection ConstantConditions
if (state.isCreated) { if (!state.isProgramCreated) return;
state.isCreated = false; state.isProgramCreated = false;
filter.onDestroy(); filter.onDestroy();
GLES20.glDeleteProgram(state.programHandle); GLES20.glDeleteProgram(state.programHandle);
state.programHandle = -1; state.programHandle = -1;
} }
}
private void maybeCreateFramebuffer(@NonNull Filter filter, boolean isLast) { private void maybeCreateFramebuffer(@NonNull Filter filter, boolean isFirst, boolean isLast) {
if (isLast) return;
State state = states.get(filter); State state = states.get(filter);
//noinspection ConstantConditions //noinspection ConstantConditions
if (!state.isFramebufferCreated) { if (state.isFramebufferCreated || isLast) return;
state.isFramebufferCreated = true; state.isFramebufferCreated = true;
state.outputTexture = new GlTexture(GLES20.GL_TEXTURE0,
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(GLES20.GL_TEXTURE_2D, state.textureId);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA,
state.size.getWidth(), state.size.getHeight(), 0, GLES20.GL_RGBA,
GLES20.GL_UNSIGNED_BYTE, null);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, 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,
GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_2D,
state.textureId, state.size.getWidth(),
0); state.size.getHeight());
state.outputFramebuffer = new GlFramebuffer();
int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER); state.outputFramebuffer.attach(state.outputTexture);
if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) {
throw new RuntimeException("Invalid framebuffer generation. Error:" + status);
}
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
}
} }
private void maybeDestroyFramebuffer(@NonNull Filter filter) { private void maybeDestroyFramebuffer(@NonNull Filter filter) {
State state = states.get(filter); State state = states.get(filter);
//noinspection ConstantConditions //noinspection ConstantConditions
if (state.isFramebufferCreated) { if (!state.isFramebufferCreated) return;
state.isFramebufferCreated = false; state.isFramebufferCreated = false;
GLES20.glDeleteFramebuffers(1, new int[]{state.framebufferId}, 0); state.outputFramebuffer.release();
state.framebufferId = -1; state.outputFramebuffer = null;
GLES20.glDeleteTextures(1, new int[]{state.textureId}, 0); state.outputTexture.release();
state.textureId = -1; state.outputTexture = null;
}
} }
private void maybeSetSize(@NonNull Filter filter) { private void maybeSetSize(@NonNull Filter filter) {
@ -196,24 +166,24 @@ public class MultiFilter implements Filter, OneParameterFilter, TwoParameterFilt
} }
} }
@Override
public void onCreate(int programHandle) {
// We'll create children during the draw() op, since some of them
// might have been added after this onCreate() is called.
}
@NonNull @NonNull
@Override @Override
public String getVertexShader() { public String getVertexShader() {
// Whatever, we won't be using this. // Whatever, we won't be using this.
return new NoFilter().getVertexShader(); return GlTextureProgram.SIMPLE_VERTEX_SHADER;
} }
@NonNull @NonNull
@Override @Override
public String getFragmentShader() { public String getFragmentShader() {
// Whatever, we won't be using this. // Whatever, we won't be using this.
return new NoFilter().getFragmentShader(); return GlTextureProgram.SIMPLE_FRAGMENT_SHADER;
}
@Override
public void onCreate(int programHandle) {
// We'll create children during the draw() op, since some of them
// might have been added after this onCreate() is called.
} }
@Override @Override
@ -221,7 +191,7 @@ public class MultiFilter implements Filter, OneParameterFilter, TwoParameterFilt
synchronized (lock) { synchronized (lock) {
for (Filter filter : filters) { for (Filter filter : filters) {
maybeDestroyFramebuffer(filter); maybeDestroyFramebuffer(filter);
maybeDestroy(filter); maybeDestroyProgram(filter);
} }
} }
} }
@ -244,9 +214,10 @@ public class MultiFilter implements Filter, OneParameterFilter, TwoParameterFilt
boolean isLast = i == filters.size() - 1; boolean isLast = i == filters.size() - 1;
Filter filter = filters.get(i); Filter filter = filters.get(i);
State state = states.get(filter); State state = states.get(filter);
maybeSetSize(filter); maybeSetSize(filter);
maybeCreate(filter, isFirst); maybeCreateProgram(filter, isFirst, isLast);
maybeCreateFramebuffer(filter, isLast); maybeCreateFramebuffer(filter, isFirst, isLast);
//noinspection ConstantConditions //noinspection ConstantConditions
GLES20.glUseProgram(state.programHandle); GLES20.glUseProgram(state.programHandle);
@ -255,7 +226,7 @@ public class MultiFilter implements Filter, OneParameterFilter, TwoParameterFilt
// Each filter outputs into its own framebuffer object, except the // Each filter outputs into its own framebuffer object, except the
// last filter, which outputs into the default framebuffer. // last filter, which outputs into the default framebuffer.
if (!isLast) { if (!isLast) {
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, state.framebufferId); state.outputFramebuffer.bind();
GLES20.glClearColor(0, 0, 0, 0); GLES20.glClearColor(0, 0, 0, 0);
} else { } else {
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
@ -267,16 +238,17 @@ public class MultiFilter implements Filter, OneParameterFilter, TwoParameterFilt
if (isFirst) { if (isFirst) {
filter.draw(timestampUs, transformMatrix); filter.draw(timestampUs, transformMatrix);
} else { } else {
filter.draw(timestampUs, GlUtils.IDENTITY_MATRIX); filter.draw(timestampUs, Egloo.IDENTITY_MATRIX);
} }
// Set the input for the next cycle: // Set the input for the next cycle:
// It is the framebuffer texture from this cycle. If this is the last // It is the framebuffer texture from this cycle. If this is the last
// filter, reset this value just to cleanup. // filter, reset this value just to cleanup.
if (!isLast) { if (!isLast) {
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, state.textureId); state.outputTexture.bind();
} else { } else {
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
} }
GLES20.glUseProgram(0); GLES20.glUseProgram(0);
@ -289,6 +261,9 @@ public class MultiFilter implements Filter, OneParameterFilter, TwoParameterFilt
public Filter copy() { public Filter copy() {
synchronized (lock) { synchronized (lock) {
MultiFilter copy = new MultiFilter(); MultiFilter copy = new MultiFilter();
if (size != null) {
copy.setSize(size.getWidth(), size.getHeight());
}
for (Filter filter : filters) { for (Filter filter : filters) {
copy.addFilter(filter.copy()); copy.addFilter(filter.copy());
} }

@ -33,6 +33,7 @@ public final class SimpleFilter extends BaseFilter {
return fragmentShader; return fragmentShader;
} }
@NonNull
@Override @Override
protected BaseFilter onCopy() { protected BaseFilter onCopy() {
return new SimpleFilter(fragmentShader); return new SimpleFilter(fragmentShader);

@ -6,7 +6,7 @@ import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.filter.OneParameterFilter; import com.otaliastudios.cameraview.filter.OneParameterFilter;
import com.otaliastudios.cameraview.internal.GlUtils; import com.otaliastudios.opengl.core.Egloo;
/** /**
* Attempts to auto-fix the frames based on histogram equalization. * Attempts to auto-fix the frames based on histogram equalization.
@ -107,7 +107,7 @@ public class AutoFixFilter extends BaseFilter implements OneParameterFilter {
public void onCreate(int programHandle) { public void onCreate(int programHandle) {
super.onCreate(programHandle); super.onCreate(programHandle);
scaleLocation = GLES20.glGetUniformLocation(programHandle, "scale"); scaleLocation = GLES20.glGetUniformLocation(programHandle, "scale");
GlUtils.checkLocation(scaleLocation, "scale"); Egloo.checkGlProgramLocation(scaleLocation, "scale");
} }
@Override @Override
@ -120,6 +120,6 @@ public class AutoFixFilter extends BaseFilter implements OneParameterFilter {
protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) {
super.onPreDraw(timestampUs, transformMatrix); super.onPreDraw(timestampUs, transformMatrix);
GLES20.glUniform1f(scaleLocation, scale); GLES20.glUniform1f(scaleLocation, scale);
GlUtils.checkError("glUniform1f"); Egloo.checkGlError("glUniform1f");
} }
} }

@ -6,7 +6,7 @@ import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.filter.OneParameterFilter; import com.otaliastudios.cameraview.filter.OneParameterFilter;
import com.otaliastudios.cameraview.internal.GlUtils; import com.otaliastudios.opengl.core.Egloo;
/** /**
* Adjusts the brightness of the frames. * Adjusts the brightness of the frames.
@ -76,7 +76,7 @@ public class BrightnessFilter extends BaseFilter implements OneParameterFilter {
public void onCreate(int programHandle) { public void onCreate(int programHandle) {
super.onCreate(programHandle); super.onCreate(programHandle);
brightnessLocation = GLES20.glGetUniformLocation(programHandle, "brightness"); brightnessLocation = GLES20.glGetUniformLocation(programHandle, "brightness");
GlUtils.checkLocation(brightnessLocation, "brightness"); Egloo.checkGlProgramLocation(brightnessLocation, "brightness");
} }
@Override @Override
@ -89,6 +89,6 @@ public class BrightnessFilter extends BaseFilter implements OneParameterFilter {
protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) {
super.onPreDraw(timestampUs, transformMatrix); super.onPreDraw(timestampUs, transformMatrix);
GLES20.glUniform1f(brightnessLocation, brightness); GLES20.glUniform1f(brightnessLocation, brightness);
GlUtils.checkError("glUniform1f"); Egloo.checkGlError("glUniform1f");
} }
} }

@ -6,7 +6,7 @@ import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.filter.OneParameterFilter; import com.otaliastudios.cameraview.filter.OneParameterFilter;
import com.otaliastudios.cameraview.internal.GlUtils; import com.otaliastudios.opengl.core.Egloo;
/** /**
* Adjusts the contrast. * Adjusts the contrast.
@ -78,7 +78,7 @@ public class ContrastFilter extends BaseFilter implements OneParameterFilter {
public void onCreate(int programHandle) { public void onCreate(int programHandle) {
super.onCreate(programHandle); super.onCreate(programHandle);
contrastLocation = GLES20.glGetUniformLocation(programHandle, "contrast"); contrastLocation = GLES20.glGetUniformLocation(programHandle, "contrast");
GlUtils.checkLocation(contrastLocation, "contrast"); Egloo.checkGlProgramLocation(contrastLocation, "contrast");
} }
@Override @Override
@ -91,6 +91,6 @@ public class ContrastFilter extends BaseFilter implements OneParameterFilter {
protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) {
super.onPreDraw(timestampUs, transformMatrix); super.onPreDraw(timestampUs, transformMatrix);
GLES20.glUniform1f(contrastLocation, contrast); GLES20.glUniform1f(contrastLocation, contrast);
GlUtils.checkError("glUniform1f"); Egloo.checkGlError("glUniform1f");
} }
} }

@ -5,7 +5,7 @@ import android.opengl.GLES20;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.internal.GlUtils; import com.otaliastudios.opengl.core.Egloo;
import java.util.Random; import java.util.Random;
@ -80,9 +80,9 @@ public class DocumentaryFilter extends BaseFilter {
public void onCreate(int programHandle) { public void onCreate(int programHandle) {
super.onCreate(programHandle); super.onCreate(programHandle);
mScaleLocation = GLES20.glGetUniformLocation(programHandle, "scale"); mScaleLocation = GLES20.glGetUniformLocation(programHandle, "scale");
GlUtils.checkLocation(mScaleLocation, "scale"); Egloo.checkGlProgramLocation(mScaleLocation, "scale");
mMaxDistLocation = GLES20.glGetUniformLocation(programHandle, "inv_max_dist"); mMaxDistLocation = GLES20.glGetUniformLocation(programHandle, "inv_max_dist");
GlUtils.checkLocation(mMaxDistLocation, "inv_max_dist"); Egloo.checkGlProgramLocation(mMaxDistLocation, "inv_max_dist");
} }
@Override @Override
@ -104,12 +104,12 @@ public class DocumentaryFilter extends BaseFilter {
scale[1] = 1f; scale[1] = 1f;
} }
GLES20.glUniform2fv(mScaleLocation, 1, scale, 0); GLES20.glUniform2fv(mScaleLocation, 1, scale, 0);
GlUtils.checkError("glUniform2fv"); Egloo.checkGlError("glUniform2fv");
float maxDist = ((float) Math.sqrt(scale[0] * scale[0] + scale[1] * scale[1])) * 0.5f; float maxDist = ((float) Math.sqrt(scale[0] * scale[0] + scale[1] * scale[1])) * 0.5f;
float invMaxDist = 1F / maxDist; float invMaxDist = 1F / maxDist;
GLES20.glUniform1f(mMaxDistLocation, invMaxDist); GLES20.glUniform1f(mMaxDistLocation, invMaxDist);
GlUtils.checkError("glUniform1f"); Egloo.checkGlError("glUniform1f");
} }
} }

@ -8,7 +8,7 @@ import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.filter.TwoParameterFilter; import com.otaliastudios.cameraview.filter.TwoParameterFilter;
import com.otaliastudios.cameraview.internal.GlUtils; import com.otaliastudios.opengl.core.Egloo;
/** /**
* Representation of input frames using only two color tones. * Representation of input frames using only two color tones.
@ -131,9 +131,9 @@ public class DuotoneFilter extends BaseFilter implements TwoParameterFilter {
public void onCreate(int programHandle) { public void onCreate(int programHandle) {
super.onCreate(programHandle); super.onCreate(programHandle);
mFirstColorLocation = GLES20.glGetUniformLocation(programHandle, "first"); mFirstColorLocation = GLES20.glGetUniformLocation(programHandle, "first");
GlUtils.checkLocation(mFirstColorLocation, "first"); Egloo.checkGlProgramLocation(mFirstColorLocation, "first");
mSecondColorLocation = GLES20.glGetUniformLocation(programHandle, "second"); mSecondColorLocation = GLES20.glGetUniformLocation(programHandle, "second");
GlUtils.checkLocation(mSecondColorLocation, "second"); Egloo.checkGlProgramLocation(mSecondColorLocation, "second");
} }
@Override @Override
@ -150,9 +150,9 @@ public class DuotoneFilter extends BaseFilter implements TwoParameterFilter {
Color.blue(mSecondColor) / 255f Color.blue(mSecondColor) / 255f
}; };
GLES20.glUniform3fv(mFirstColorLocation, 1, first, 0); GLES20.glUniform3fv(mFirstColorLocation, 1, first, 0);
GlUtils.checkError("glUniform3fv"); Egloo.checkGlError("glUniform3fv");
GLES20.glUniform3fv(mSecondColorLocation, 1, second, 0); GLES20.glUniform3fv(mSecondColorLocation, 1, second, 0);
GlUtils.checkError("glUniform3fv"); Egloo.checkGlError("glUniform3fv");
} }
@Override @Override

@ -6,7 +6,7 @@ import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.filter.OneParameterFilter; import com.otaliastudios.cameraview.filter.OneParameterFilter;
import com.otaliastudios.cameraview.internal.GlUtils; import com.otaliastudios.opengl.core.Egloo;
/** /**
* Applies back-light filling to the frames. * Applies back-light filling to the frames.
@ -82,9 +82,9 @@ public class FillLightFilter extends BaseFilter implements OneParameterFilter {
public void onCreate(int programHandle) { public void onCreate(int programHandle) {
super.onCreate(programHandle); super.onCreate(programHandle);
multiplierLocation = GLES20.glGetUniformLocation(programHandle, "mult"); multiplierLocation = GLES20.glGetUniformLocation(programHandle, "mult");
GlUtils.checkLocation(multiplierLocation, "mult"); Egloo.checkGlProgramLocation(multiplierLocation, "mult");
gammaLocation = GLES20.glGetUniformLocation(programHandle, "igamma"); gammaLocation = GLES20.glGetUniformLocation(programHandle, "igamma");
GlUtils.checkLocation(gammaLocation, "igamma"); Egloo.checkGlProgramLocation(gammaLocation, "igamma");
} }
@Override @Override
@ -100,12 +100,12 @@ public class FillLightFilter extends BaseFilter implements OneParameterFilter {
float amount = 1.0f - strength; float amount = 1.0f - strength;
float multiplier = 1.0f / (amount * 0.7f + 0.3f); float multiplier = 1.0f / (amount * 0.7f + 0.3f);
GLES20.glUniform1f(multiplierLocation, multiplier); GLES20.glUniform1f(multiplierLocation, multiplier);
GlUtils.checkError("glUniform1f"); Egloo.checkGlError("glUniform1f");
float fadeGamma = 0.3f; float fadeGamma = 0.3f;
float faded = fadeGamma + (1.0f - fadeGamma) * multiplier; float faded = fadeGamma + (1.0f - fadeGamma) * multiplier;
float gamma = 1.0f / faded; float gamma = 1.0f / faded;
GLES20.glUniform1f(gammaLocation, gamma); GLES20.glUniform1f(gammaLocation, gamma);
GlUtils.checkError("glUniform1f"); Egloo.checkGlError("glUniform1f");
} }
} }

@ -6,7 +6,7 @@ import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.filter.OneParameterFilter; import com.otaliastudios.cameraview.filter.OneParameterFilter;
import com.otaliastudios.cameraview.internal.GlUtils; import com.otaliastudios.opengl.core.Egloo;
/** /**
* Applies gamma correction to the frames. * Applies gamma correction to the frames.
@ -73,7 +73,7 @@ public class GammaFilter extends BaseFilter implements OneParameterFilter {
public void onCreate(int programHandle) { public void onCreate(int programHandle) {
super.onCreate(programHandle); super.onCreate(programHandle);
gammaLocation = GLES20.glGetUniformLocation(programHandle, "gamma"); gammaLocation = GLES20.glGetUniformLocation(programHandle, "gamma");
GlUtils.checkLocation(gammaLocation, "gamma"); Egloo.checkGlProgramLocation(gammaLocation, "gamma");
} }
@Override @Override
@ -86,6 +86,6 @@ public class GammaFilter extends BaseFilter implements OneParameterFilter {
protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) {
super.onPreDraw(timestampUs, transformMatrix); super.onPreDraw(timestampUs, transformMatrix);
GLES20.glUniform1f(gammaLocation, gamma); GLES20.glUniform1f(gammaLocation, gamma);
GlUtils.checkError("glUniform1f"); Egloo.checkGlError("glUniform1f");
} }
} }

@ -6,7 +6,7 @@ import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.filter.OneParameterFilter; import com.otaliastudios.cameraview.filter.OneParameterFilter;
import com.otaliastudios.cameraview.internal.GlUtils; import com.otaliastudios.opengl.core.Egloo;
import java.util.Random; import java.util.Random;
@ -122,11 +122,11 @@ public class GrainFilter extends BaseFilter implements OneParameterFilter {
public void onCreate(int programHandle) { public void onCreate(int programHandle) {
super.onCreate(programHandle); super.onCreate(programHandle);
strengthLocation = GLES20.glGetUniformLocation(programHandle, "scale"); strengthLocation = GLES20.glGetUniformLocation(programHandle, "scale");
GlUtils.checkLocation(strengthLocation, "scale"); Egloo.checkGlProgramLocation(strengthLocation, "scale");
stepXLocation = GLES20.glGetUniformLocation(programHandle, "stepX"); stepXLocation = GLES20.glGetUniformLocation(programHandle, "stepX");
GlUtils.checkLocation(stepXLocation, "stepX"); Egloo.checkGlProgramLocation(stepXLocation, "stepX");
stepYLocation = GLES20.glGetUniformLocation(programHandle, "stepY"); stepYLocation = GLES20.glGetUniformLocation(programHandle, "stepY");
GlUtils.checkLocation(stepYLocation, "stepY"); Egloo.checkGlProgramLocation(stepYLocation, "stepY");
} }
@Override @Override
@ -141,10 +141,10 @@ public class GrainFilter extends BaseFilter implements OneParameterFilter {
protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) {
super.onPreDraw(timestampUs, transformMatrix); super.onPreDraw(timestampUs, transformMatrix);
GLES20.glUniform1f(strengthLocation, strength); GLES20.glUniform1f(strengthLocation, strength);
GlUtils.checkError("glUniform1f"); Egloo.checkGlError("glUniform1f");
GLES20.glUniform1f(stepXLocation, 0.5f / width); GLES20.glUniform1f(stepXLocation, 0.5f / width);
GlUtils.checkError("glUniform1f"); Egloo.checkGlError("glUniform1f");
GLES20.glUniform1f(stepYLocation, 0.5f / height); GLES20.glUniform1f(stepYLocation, 0.5f / height);
GlUtils.checkError("glUniform1f"); Egloo.checkGlError("glUniform1f");
} }
} }

@ -6,7 +6,7 @@ import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.filter.OneParameterFilter; import com.otaliastudios.cameraview.filter.OneParameterFilter;
import com.otaliastudios.cameraview.internal.GlUtils; import com.otaliastudios.opengl.core.Egloo;
/** /**
* Applies a hue effect on the input frames. * Applies a hue effect on the input frames.
@ -86,7 +86,7 @@ public class HueFilter extends BaseFilter implements OneParameterFilter {
public void onCreate(int programHandle) { public void onCreate(int programHandle) {
super.onCreate(programHandle); super.onCreate(programHandle);
hueLocation = GLES20.glGetUniformLocation(programHandle, "hue"); hueLocation = GLES20.glGetUniformLocation(programHandle, "hue");
GlUtils.checkLocation(hueLocation, "hue"); Egloo.checkGlProgramLocation(hueLocation, "hue");
} }
@Override @Override
@ -101,6 +101,6 @@ public class HueFilter extends BaseFilter implements OneParameterFilter {
// map it on 360 degree circle // map it on 360 degree circle
float shaderHue = ((hue - 45) / 45f + 0.5f) * -1; float shaderHue = ((hue - 45) / 45f + 0.5f) * -1;
GLES20.glUniform1f(hueLocation, shaderHue); GLES20.glUniform1f(hueLocation, shaderHue);
GlUtils.checkError("glUniform1f"); Egloo.checkGlError("glUniform1f");
} }
} }

@ -5,7 +5,7 @@ import android.opengl.GLES20;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.internal.GlUtils; import com.otaliastudios.opengl.core.Egloo;
import java.util.Random; import java.util.Random;
@ -122,13 +122,13 @@ public class LomoishFilter extends BaseFilter {
public void onCreate(int programHandle) { public void onCreate(int programHandle) {
super.onCreate(programHandle); super.onCreate(programHandle);
scaleLocation = GLES20.glGetUniformLocation(programHandle, "scale"); scaleLocation = GLES20.glGetUniformLocation(programHandle, "scale");
GlUtils.checkLocation(scaleLocation, "scale"); Egloo.checkGlProgramLocation(scaleLocation, "scale");
maxDistLocation = GLES20.glGetUniformLocation(programHandle, "inv_max_dist"); maxDistLocation = GLES20.glGetUniformLocation(programHandle, "inv_max_dist");
GlUtils.checkLocation(maxDistLocation, "inv_max_dist"); Egloo.checkGlProgramLocation(maxDistLocation, "inv_max_dist");
stepSizeXLocation = GLES20.glGetUniformLocation(programHandle, "stepsizeX"); stepSizeXLocation = GLES20.glGetUniformLocation(programHandle, "stepsizeX");
GlUtils.checkLocation(stepSizeXLocation, "stepsizeX"); Egloo.checkGlProgramLocation(stepSizeXLocation, "stepsizeX");
stepSizeYLocation = GLES20.glGetUniformLocation(programHandle, "stepsizeY"); stepSizeYLocation = GLES20.glGetUniformLocation(programHandle, "stepsizeY");
GlUtils.checkLocation(stepSizeYLocation, "stepsizeY"); Egloo.checkGlProgramLocation(stepSizeYLocation, "stepsizeY");
} }
@Override @Override
@ -153,12 +153,12 @@ public class LomoishFilter extends BaseFilter {
} }
float maxDist = ((float) Math.sqrt(scale[0] * scale[0] + scale[1] * scale[1])) * 0.5f; float maxDist = ((float) Math.sqrt(scale[0] * scale[0] + scale[1] * scale[1])) * 0.5f;
GLES20.glUniform2fv(scaleLocation, 1, scale, 0); GLES20.glUniform2fv(scaleLocation, 1, scale, 0);
GlUtils.checkError("glUniform2fv"); Egloo.checkGlError("glUniform2fv");
GLES20.glUniform1f(maxDistLocation, 1.0F / maxDist); GLES20.glUniform1f(maxDistLocation, 1.0F / maxDist);
GlUtils.checkError("glUniform1f"); Egloo.checkGlError("glUniform1f");
GLES20.glUniform1f(stepSizeXLocation, 1.0F / width); GLES20.glUniform1f(stepSizeXLocation, 1.0F / width);
GlUtils.checkError("glUniform1f"); Egloo.checkGlError("glUniform1f");
GLES20.glUniform1f(stepSizeYLocation, 1.0F / height); GLES20.glUniform1f(stepSizeYLocation, 1.0F / height);
GlUtils.checkError("glUniform1f"); Egloo.checkGlError("glUniform1f");
} }
} }

@ -6,7 +6,7 @@ import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.filter.OneParameterFilter; import com.otaliastudios.cameraview.filter.OneParameterFilter;
import com.otaliastudios.cameraview.internal.GlUtils; import com.otaliastudios.opengl.core.Egloo;
/** /**
* Adjusts color saturation. * Adjusts color saturation.
@ -93,9 +93,9 @@ public class SaturationFilter extends BaseFilter implements OneParameterFilter {
public void onCreate(int programHandle) { public void onCreate(int programHandle) {
super.onCreate(programHandle); super.onCreate(programHandle);
scaleLocation = GLES20.glGetUniformLocation(programHandle, "scale"); scaleLocation = GLES20.glGetUniformLocation(programHandle, "scale");
GlUtils.checkLocation(scaleLocation, "scale"); Egloo.checkGlProgramLocation(scaleLocation, "scale");
exponentsLocation = GLES20.glGetUniformLocation(programHandle, "exponents"); exponentsLocation = GLES20.glGetUniformLocation(programHandle, "exponents");
GlUtils.checkLocation(exponentsLocation, "exponents"); Egloo.checkGlProgramLocation(exponentsLocation, "exponents");
} }
@Override @Override
@ -110,18 +110,18 @@ public class SaturationFilter extends BaseFilter implements OneParameterFilter {
super.onPreDraw(timestampUs, transformMatrix); super.onPreDraw(timestampUs, transformMatrix);
if (scale > 0.0f) { if (scale > 0.0f) {
GLES20.glUniform1f(scaleLocation, 0F); GLES20.glUniform1f(scaleLocation, 0F);
GlUtils.checkError("glUniform1f"); Egloo.checkGlError("glUniform1f");
GLES20.glUniform3f(exponentsLocation, GLES20.glUniform3f(exponentsLocation,
(0.9f * scale) + 1.0f, (0.9f * scale) + 1.0f,
(2.1f * scale) + 1.0f, (2.1f * scale) + 1.0f,
(2.7f * scale) + 1.0f (2.7f * scale) + 1.0f
); );
GlUtils.checkError("glUniform3f"); Egloo.checkGlError("glUniform3f");
} else { } else {
GLES20.glUniform1f(scaleLocation, 1.0F + scale); GLES20.glUniform1f(scaleLocation, 1.0F + scale);
GlUtils.checkError("glUniform1f"); Egloo.checkGlError("glUniform1f");
GLES20.glUniform3f(exponentsLocation, 0F, 0F, 0F); GLES20.glUniform3f(exponentsLocation, 0F, 0F, 0F);
GlUtils.checkError("glUniform3f"); Egloo.checkGlError("glUniform3f");
} }
} }
} }

@ -6,7 +6,7 @@ import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.filter.OneParameterFilter; import com.otaliastudios.cameraview.filter.OneParameterFilter;
import com.otaliastudios.cameraview.internal.GlUtils; import com.otaliastudios.opengl.core.Egloo;
/** /**
* Sharpens the input frames. * Sharpens the input frames.
@ -100,11 +100,11 @@ public class SharpnessFilter extends BaseFilter implements OneParameterFilter {
public void onCreate(int programHandle) { public void onCreate(int programHandle) {
super.onCreate(programHandle); super.onCreate(programHandle);
scaleLocation = GLES20.glGetUniformLocation(programHandle, "scale"); scaleLocation = GLES20.glGetUniformLocation(programHandle, "scale");
GlUtils.checkLocation(scaleLocation, "scale"); Egloo.checkGlProgramLocation(scaleLocation, "scale");
stepSizeXLocation = GLES20.glGetUniformLocation(programHandle, "stepsizeX"); stepSizeXLocation = GLES20.glGetUniformLocation(programHandle, "stepsizeX");
GlUtils.checkLocation(stepSizeXLocation, "stepsizeX"); Egloo.checkGlProgramLocation(stepSizeXLocation, "stepsizeX");
stepSizeYLocation = GLES20.glGetUniformLocation(programHandle, "stepsizeY"); stepSizeYLocation = GLES20.glGetUniformLocation(programHandle, "stepsizeY");
GlUtils.checkLocation(stepSizeYLocation, "stepsizeY"); Egloo.checkGlProgramLocation(stepSizeYLocation, "stepsizeY");
} }
@Override @Override
@ -119,10 +119,10 @@ public class SharpnessFilter extends BaseFilter implements OneParameterFilter {
protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) {
super.onPreDraw(timestampUs, transformMatrix); super.onPreDraw(timestampUs, transformMatrix);
GLES20.glUniform1f(scaleLocation, scale); GLES20.glUniform1f(scaleLocation, scale);
GlUtils.checkError("glUniform1f"); Egloo.checkGlError("glUniform1f");
GLES20.glUniform1f(stepSizeXLocation, 1.0F / width); GLES20.glUniform1f(stepSizeXLocation, 1.0F / width);
GlUtils.checkError("glUniform1f"); Egloo.checkGlError("glUniform1f");
GLES20.glUniform1f(stepSizeYLocation, 1.0F / height); GLES20.glUniform1f(stepSizeYLocation, 1.0F / height);
GlUtils.checkError("glUniform1f"); Egloo.checkGlError("glUniform1f");
} }
} }

@ -6,7 +6,7 @@ import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.filter.OneParameterFilter; import com.otaliastudios.cameraview.filter.OneParameterFilter;
import com.otaliastudios.cameraview.internal.GlUtils; import com.otaliastudios.opengl.core.Egloo;
/** /**
* Adjusts color temperature. * Adjusts color temperature.
@ -84,7 +84,7 @@ public class TemperatureFilter extends BaseFilter implements OneParameterFilter
public void onCreate(int programHandle) { public void onCreate(int programHandle) {
super.onCreate(programHandle); super.onCreate(programHandle);
scaleLocation = GLES20.glGetUniformLocation(programHandle, "scale"); scaleLocation = GLES20.glGetUniformLocation(programHandle, "scale");
GlUtils.checkLocation(scaleLocation, "scale"); Egloo.checkGlProgramLocation(scaleLocation, "scale");
} }
@Override @Override
@ -97,6 +97,6 @@ public class TemperatureFilter extends BaseFilter implements OneParameterFilter
protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) {
super.onPreDraw(timestampUs, transformMatrix); super.onPreDraw(timestampUs, transformMatrix);
GLES20.glUniform1f(scaleLocation, scale); GLES20.glUniform1f(scaleLocation, scale);
GlUtils.checkError("glUniform1f"); Egloo.checkGlError("glUniform1f");
} }
} }

@ -8,7 +8,7 @@ import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.filter.OneParameterFilter; import com.otaliastudios.cameraview.filter.OneParameterFilter;
import com.otaliastudios.cameraview.internal.GlUtils; import com.otaliastudios.opengl.core.Egloo;
/** /**
@ -81,7 +81,7 @@ public class TintFilter extends BaseFilter implements OneParameterFilter {
public void onCreate(int programHandle) { public void onCreate(int programHandle) {
super.onCreate(programHandle); super.onCreate(programHandle);
tintLocation = GLES20.glGetUniformLocation(programHandle, "tint"); tintLocation = GLES20.glGetUniformLocation(programHandle, "tint");
GlUtils.checkLocation(tintLocation, "tint"); Egloo.checkGlProgramLocation(tintLocation, "tint");
} }
@Override @Override
@ -99,6 +99,6 @@ public class TintFilter extends BaseFilter implements OneParameterFilter {
Color.blue(tint) / 255f Color.blue(tint) / 255f
}; };
GLES20.glUniform3fv(tintLocation, 1, channels, 0); GLES20.glUniform3fv(tintLocation, 1, channels, 0);
GlUtils.checkError("glUniform3fv"); Egloo.checkGlError("glUniform3fv");
} }
} }

@ -6,7 +6,7 @@ import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.filter.TwoParameterFilter; import com.otaliastudios.cameraview.filter.TwoParameterFilter;
import com.otaliastudios.cameraview.internal.GlUtils; import com.otaliastudios.opengl.core.Egloo;
/** /**
@ -126,13 +126,13 @@ public class VignetteFilter extends BaseFilter implements TwoParameterFilter {
public void onCreate(int programHandle) { public void onCreate(int programHandle) {
super.onCreate(programHandle); super.onCreate(programHandle);
mRangeLocation = GLES20.glGetUniformLocation(programHandle, "range"); mRangeLocation = GLES20.glGetUniformLocation(programHandle, "range");
GlUtils.checkLocation(mRangeLocation, "range"); Egloo.checkGlProgramLocation(mRangeLocation, "range");
mMaxDistLocation = GLES20.glGetUniformLocation(programHandle, "inv_max_dist"); mMaxDistLocation = GLES20.glGetUniformLocation(programHandle, "inv_max_dist");
GlUtils.checkLocation(mMaxDistLocation, "inv_max_dist"); Egloo.checkGlProgramLocation(mMaxDistLocation, "inv_max_dist");
mShadeLocation = GLES20.glGetUniformLocation(programHandle, "shade"); mShadeLocation = GLES20.glGetUniformLocation(programHandle, "shade");
GlUtils.checkLocation(mShadeLocation, "shade"); Egloo.checkGlProgramLocation(mShadeLocation, "shade");
mScaleLocation = GLES20.glGetUniformLocation(programHandle, "scale"); mScaleLocation = GLES20.glGetUniformLocation(programHandle, "scale");
GlUtils.checkLocation(mScaleLocation, "scale"); Egloo.checkGlProgramLocation(mScaleLocation, "scale");
} }
@Override @Override
@ -156,20 +156,20 @@ public class VignetteFilter extends BaseFilter implements TwoParameterFilter {
scale[1] = 1f; scale[1] = 1f;
} }
GLES20.glUniform2fv(mScaleLocation, 1, scale, 0); GLES20.glUniform2fv(mScaleLocation, 1, scale, 0);
GlUtils.checkError("glUniform2fv"); Egloo.checkGlError("glUniform2fv");
float maxDist = ((float) Math.sqrt(scale[0] * scale[0] + scale[1] * scale[1])) * 0.5f; float maxDist = ((float) Math.sqrt(scale[0] * scale[0] + scale[1] * scale[1])) * 0.5f;
GLES20.glUniform1f(mMaxDistLocation, 1F / maxDist); GLES20.glUniform1f(mMaxDistLocation, 1F / maxDist);
GlUtils.checkError("glUniform1f"); Egloo.checkGlError("glUniform1f");
GLES20.glUniform1f(mShadeLocation, mShade); GLES20.glUniform1f(mShadeLocation, mShade);
GlUtils.checkError("glUniform1f"); Egloo.checkGlError("glUniform1f");
// The 'range' is between 1.3 to 0.6. When scale is zero then range is 1.3 // The 'range' is between 1.3 to 0.6. When scale is zero then range is 1.3
// which means no vignette at all because the luminousity difference is // which means no vignette at all because the luminousity difference is
// less than 1/256 and will cause nothing. // less than 1/256 and will cause nothing.
float range = (1.30f - (float) Math.sqrt(mScale) * 0.7f); float range = (1.30f - (float) Math.sqrt(mScale) * 0.7f);
GLES20.glUniform1f(mRangeLocation, range); GLES20.glUniform1f(mRangeLocation, range);
GlUtils.checkError("glUniform1f"); Egloo.checkGlError("glUniform1f");
} }
} }

@ -1,4 +1,4 @@
package com.otaliastudios.cameraview.internal.utils; package com.otaliastudios.cameraview.internal;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.media.CamcorderProfile; import android.media.CamcorderProfile;

@ -1,4 +1,4 @@
package com.otaliastudios.cameraview.internal.utils; package com.otaliastudios.cameraview.internal;
import android.graphics.Rect; import android.graphics.Rect;

@ -1,4 +1,4 @@
package com.otaliastudios.cameraview.internal.utils; package com.otaliastudios.cameraview.internal;
import androidx.exifinterface.media.ExifInterface; import androidx.exifinterface.media.ExifInterface;

@ -0,0 +1,97 @@
package com.otaliastudios.cameraview.internal;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.filter.Filter;
import com.otaliastudios.cameraview.filter.NoFilter;
import com.otaliastudios.opengl.core.Egloo;
import com.otaliastudios.opengl.program.GlProgram;
import com.otaliastudios.opengl.texture.GlTexture;
public class GlTextureDrawer {
private final static String TAG = GlTextureDrawer.class.getSimpleName();
private final static CameraLogger LOG = CameraLogger.create(TAG);
private final static int TEXTURE_TARGET = GLES11Ext.GL_TEXTURE_EXTERNAL_OES;
private final static int TEXTURE_UNIT = GLES20.GL_TEXTURE0;
private final GlTexture mTexture;
private float[] mTextureTransform = Egloo.IDENTITY_MATRIX.clone();
@NonNull
private Filter mFilter = new NoFilter();
private Filter mPendingFilter = null;
private int mProgramHandle = -1;
@SuppressWarnings("unused")
public GlTextureDrawer() {
this(new GlTexture(TEXTURE_UNIT, TEXTURE_TARGET));
}
@SuppressWarnings("unused")
public GlTextureDrawer(int textureId) {
this(new GlTexture(TEXTURE_UNIT, TEXTURE_TARGET, textureId));
}
@SuppressWarnings("WeakerAccess")
public GlTextureDrawer(@NonNull GlTexture texture) {
mTexture = texture;
}
public void setFilter(@NonNull Filter filter) {
mPendingFilter = filter;
}
@NonNull
public GlTexture getTexture() {
return mTexture;
}
@NonNull
public float[] getTextureTransform() {
return mTextureTransform;
}
public void setTextureTransform(@NonNull float[] textureTransform) {
mTextureTransform = textureTransform;
}
public void draw(final long timestampUs) {
if (mPendingFilter != null) {
release();
mFilter = mPendingFilter;
mPendingFilter = null;
}
if (mProgramHandle == -1) {
mProgramHandle = GlProgram.create(
mFilter.getVertexShader(),
mFilter.getFragmentShader());
mFilter.onCreate(mProgramHandle);
Egloo.checkGlError("program creation");
}
GLES20.glUseProgram(mProgramHandle);
Egloo.checkGlError("glUseProgram(handle)");
mTexture.bind();
mFilter.draw(timestampUs, mTextureTransform);
mTexture.unbind();
GLES20.glUseProgram(0);
Egloo.checkGlError("glUseProgram(0)");
}
public void release() {
if (mProgramHandle == -1) return;
mFilter.onDestroy();
GLES20.glDeleteProgram(mProgramHandle);
mProgramHandle = -1;
}
}

@ -1,96 +0,0 @@
package com.otaliastudios.cameraview.internal;
import android.opengl.GLES20;
import android.opengl.Matrix;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.CameraLogger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
public class GlUtils {
private final static String TAG = GlUtils.class.getSimpleName();
private final static CameraLogger LOG = CameraLogger.create(TAG);
// Identity matrix for general use.
public static final float[] IDENTITY_MATRIX = new float[16];
static {
Matrix.setIdentityM(IDENTITY_MATRIX, 0);
}
public static void checkError(@NonNull String opName) {
int error = GLES20.glGetError();
if (error != GLES20.GL_NO_ERROR) {
String message = LOG.e("Error during", opName, "glError 0x",
Integer.toHexString(error));
throw new RuntimeException(message);
}
}
public static void checkLocation(int location, @NonNull String name) {
if (location < 0) {
String message = LOG.e("Unable to locate", name, "in program");
throw new RuntimeException(message);
}
}
// Compiles the given shader, returns a handle.
@SuppressWarnings("WeakerAccess")
public static int loadShader(int shaderType, @NonNull String source) {
int shader = GLES20.glCreateShader(shaderType);
checkError("glCreateShader type=" + shaderType);
GLES20.glShaderSource(shader, source);
GLES20.glCompileShader(shader);
int[] compiled = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {
LOG.e("Could not compile shader", shaderType, ":",
GLES20.glGetShaderInfoLog(shader));
GLES20.glDeleteShader(shader);
shader = 0;
}
return shader;
}
// Creates a program with given vertex shader and pixel shader.
public static int createProgram(@NonNull String vertexSource, @NonNull String fragmentSource) {
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
if (vertexShader == 0) return 0;
int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
if (pixelShader == 0) return 0;
int program = GLES20.glCreateProgram();
checkError("glCreateProgram");
if (program == 0) {
LOG.e("Could not create program");
}
GLES20.glAttachShader(program, vertexShader);
checkError("glAttachShader");
GLES20.glAttachShader(program, pixelShader);
checkError("glAttachShader");
GLES20.glLinkProgram(program);
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
if (linkStatus[0] != GLES20.GL_TRUE) {
LOG.e("Could not link program:", GLES20.glGetProgramInfoLog(program));
GLES20.glDeleteProgram(program);
program = 0;
}
return program;
}
// Allocates a direct float buffer, and populates it with the float array data.
public static FloatBuffer floatBuffer(@NonNull float[] coords) {
// Allocate a direct ByteBuffer, using 4 bytes per float, and copy coords into it.
ByteBuffer bb = ByteBuffer.allocateDirect(coords.length * 4);
bb.order(ByteOrder.nativeOrder());
FloatBuffer fb = bb.asFloatBuffer();
fb.put(coords);
fb.position(0);
return fb;
}
}

@ -6,7 +6,6 @@ import android.graphics.SurfaceTexture;
import android.opengl.GLES11Ext; import android.opengl.GLES11Ext;
import android.opengl.GLES20; import android.opengl.GLES20;
import android.view.Surface; import android.view.Surface;
import com.otaliastudios.cameraview.internal.egl.EglViewport;
import com.otaliastudios.cameraview.preview.RendererThread; import com.otaliastudios.cameraview.preview.RendererThread;
@ -84,7 +83,7 @@ import com.otaliastudios.cameraview.preview.RendererThread;
* *
* This makes no sense, since overlaySurfaceTexture.updateTexImage() is setting it to * This makes no sense, since overlaySurfaceTexture.updateTexImage() is setting it to
* overlayTextureId anyway, but it fixes the issue. Specifically, after any draw operation with * overlayTextureId anyway, but it fixes the issue. Specifically, after any draw operation with
* {@link EglViewport}, the bound texture is reset to 0 so this must be undone here. We offer: * {@link GlTextureDrawer}, the bound texture is reset to 0 so this must be undone here. We offer:
* *
* - {@link #beforeOverlayUpdateTexImage()} to be called before the * - {@link #beforeOverlayUpdateTexImage()} to be called before the
* {@link SurfaceTexture#updateTexImage()} call * {@link SurfaceTexture#updateTexImage()} call
@ -92,7 +91,7 @@ import com.otaliastudios.cameraview.preview.RendererThread;
* *
* Since updating and rendering can happen on different threads with a shared EGL context, * Since updating and rendering can happen on different threads with a shared EGL context,
* in case they do, the {@link #beforeOverlayUpdateTexImage()}, the actual updateTexImage() and * in case they do, the {@link #beforeOverlayUpdateTexImage()}, the actual updateTexImage() and
* finally the {@link EglViewport} drawing operations should be synchronized with a lock. * finally the {@link GlTextureDrawer} drawing operations should be synchronized with a lock.
* *
* REFERENCES * REFERENCES
* https://github.com/natario1/CameraView/issues/514 * https://github.com/natario1/CameraView/issues/514

@ -1,4 +1,4 @@
package com.otaliastudios.cameraview.internal.utils; package com.otaliastudios.cameraview.internal;
import android.content.Context; import android.content.Context;
import android.hardware.SensorManager; import android.hardware.SensorManager;

@ -1,4 +1,4 @@
package com.otaliastudios.cameraview.internal.utils; package com.otaliastudios.cameraview.internal;
import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.CameraLogger;

@ -1,4 +1,4 @@
package com.otaliastudios.cameraview.internal.utils; package com.otaliastudios.cameraview.internal;
import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.Size;

@ -1,4 +1,4 @@
package com.otaliastudios.cameraview.internal.utils; package com.otaliastudios.cameraview.internal;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;

@ -1,244 +0,0 @@
/*
* Copyright 2013 Google Inc. All rights reserved.
*
* 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.otaliastudios.cameraview.internal.egl;
import android.graphics.Bitmap;
import android.opengl.EGL14;
import android.opengl.EGLSurface;
import android.opengl.GLES20;
import android.os.Build;
import androidx.annotation.RequiresApi;
import android.util.Log;
import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.internal.GlUtils;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* Common base class for EGL surfaces.
* <p>
* There can be multiple surfaces associated with a single context.
*/
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class EglBaseSurface {
protected static final String TAG = EglBaseSurface.class.getSimpleName();
private final static CameraLogger LOG = CameraLogger.create(TAG);
// EglCore object we're associated with. It may be associated with multiple surfaces.
protected EglCore mEglCore;
private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE;
private int mWidth = -1;
private int mHeight = -1;
public EglBaseSurface(EglCore eglCore) {
mEglCore = eglCore;
}
/**
* Creates a window surface.
* <p>
* @param surface May be a Surface or SurfaceTexture.
*/
public void createWindowSurface(Object surface) {
if (mEGLSurface != EGL14.EGL_NO_SURFACE) {
throw new IllegalStateException("surface already created");
}
mEGLSurface = mEglCore.createWindowSurface(surface);
// Don't cache width/height here, because the size of the underlying surface can change
// out from under us (see e.g. HardwareScalerActivity).
//mWidth = mEglCore.querySurface(mEGLSurface, EGL14.EGL_WIDTH);
//mHeight = mEglCore.querySurface(mEGLSurface, EGL14.EGL_HEIGHT);
}
/**
* Creates an off-screen surface.
*/
public void createOffscreenSurface(int width, int height) {
if (mEGLSurface != EGL14.EGL_NO_SURFACE) {
throw new IllegalStateException("surface already created");
}
mEGLSurface = mEglCore.createOffscreenSurface(width, height);
mWidth = width;
mHeight = height;
}
/**
* Returns the surface's width, in pixels.
* <p>
* If this is called on a window surface, and the underlying surface is in the process
* of changing size, we may not see the new size right away (e.g. in the "surfaceChanged"
* callback). The size should match after the next buffer swap.
*/
public int getWidth() {
if (mWidth < 0) {
return mEglCore.querySurface(mEGLSurface, EGL14.EGL_WIDTH);
} else {
return mWidth;
}
}
/**
* Returns the surface's height, in pixels.
*/
public int getHeight() {
if (mHeight < 0) {
return mEglCore.querySurface(mEGLSurface, EGL14.EGL_HEIGHT);
} else {
return mHeight;
}
}
/**
* Release the EGL surface.
*/
public void releaseEglSurface() {
mEglCore.releaseSurface(mEGLSurface);
mEGLSurface = EGL14.EGL_NO_SURFACE;
mWidth = mHeight = -1;
}
/**
* Makes our EGL context and surface current.
*/
public void makeCurrent() {
mEglCore.makeCurrent(mEGLSurface);
}
/**
* Makes our EGL context and surface current for drawing, using the supplied surface
* for reading.
*/
public void makeCurrentReadFrom(EglBaseSurface readSurface) {
mEglCore.makeCurrent(mEGLSurface, readSurface.mEGLSurface);
}
/**
* Calls eglSwapBuffers. Use this to "publish" the current frame.
*
* @return false on failure
*/
public boolean swapBuffers() {
boolean result = mEglCore.swapBuffers(mEGLSurface);
if (!result) {
Log.d(TAG, "WARNING: swapBuffers() failed");
}
return result;
}
/**
* Sends the presentation time stamp to EGL.
* https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_presentation_time.txt
*
* @param nsecs Timestamp, in nanoseconds.
*/
public void setPresentationTime(long nsecs) {
mEglCore.setPresentationTime(mEGLSurface, nsecs);
}
/**
* Saves the EGL surface to a file.
* <p>
* Expects that this object's EGL surface is current.
*/
public void saveFrameToFile(File file) throws IOException {
if (!mEglCore.isCurrent(mEGLSurface)) {
throw new RuntimeException("Expected EGL context/surface is not current");
}
// glReadPixels fills in a "direct" ByteBuffer with what is essentially big-endian RGBA
// data (i.e. a byte of red, followed by a byte of green...). While the Bitmap
// constructor that takes an int[] wants little-endian ARGB (blue/red swapped), the
// Bitmap "copy pixels" method wants the same format GL provides.
//
// Ideally we'd have some way to re-use the ByteBuffer, especially if we're calling
// here often.
//
// Making this even more interesting is the upside-down nature of GL, which means
// our output will look upside down relative to what appears on screen if the
// typical GL conventions are used.
String filename = file.toString();
int width = getWidth();
int height = getHeight();
ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4);
buf.order(ByteOrder.LITTLE_ENDIAN);
GLES20.glReadPixels(0, 0, width, height,
GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);
GlUtils.checkError("glReadPixels");
buf.rewind();
BufferedOutputStream bos = null;
try {
bos = new BufferedOutputStream(new FileOutputStream(filename));
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bmp.copyPixelsFromBuffer(buf);
bmp.compress(Bitmap.CompressFormat.PNG, 90, bos);
bmp.recycle();
} finally {
if (bos != null) bos.close();
}
}
/**
* Saves the EGL surface to given format.
* <p>
* Expects that this object's EGL surface is current.
*/
public byte[] saveFrameTo(Bitmap.CompressFormat compressFormat) {
if (!mEglCore.isCurrent(mEGLSurface)) {
throw new RuntimeException("Expected EGL context/surface is not current");
}
// glReadPixels fills in a "direct" ByteBuffer with what is essentially big-endian RGBA
// data (i.e. a byte of red, followed by a byte of green...). While the Bitmap
// constructor that takes an int[] wants little-endian ARGB (blue/red swapped), the
// Bitmap "copy pixels" method wants the same format GL provides.
//
// Ideally we'd have some way to re-use the ByteBuffer, especially if we're calling
// here often.
//
// Making this even more interesting is the upside-down nature of GL, which means
// our output will look upside down relative to what appears on screen if the
// typical GL conventions are used.
int width = getWidth();
int height = getHeight();
ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4);
buf.order(ByteOrder.LITTLE_ENDIAN);
GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE,
buf);
GlUtils.checkError("glReadPixels");
buf.rewind();
ByteArrayOutputStream bos = new ByteArrayOutputStream(buf.array().length);
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bmp.copyPixelsFromBuffer(buf);
bmp.compress(compressFormat, 90, bos);
bmp.recycle();
return bos.toByteArray();
}
}

@ -1,364 +0,0 @@
/*
* Copyright 2013 Google Inc. All rights reserved.
*
* 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.otaliastudios.cameraview.internal.egl;
import android.graphics.SurfaceTexture;
import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLExt;
import android.opengl.EGLSurface;
import android.os.Build;
import androidx.annotation.RequiresApi;
import android.util.Log;
import android.view.Surface;
/**
* -- from grafika --
*
* Core EGL state (display, context, config).
* <p>
* The EGLContext must only be attached to one thread at a time. This class is not thread-safe.
*/
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public final class EglCore {
private static final String TAG = EglCore.class.getSimpleName();
/**
* Constructor flag: surface must be recordable. This discourages EGL from using a
* pixel format that cannot be converted efficiently to something usable by the video
* encoder.
*/
public static final int FLAG_RECORDABLE = 0x01;
/**
* Constructor flag: ask for GLES3, fall back to GLES2 if not available. Without this
* flag, GLES2 is used.
*/
public static final int FLAG_TRY_GLES3 = 0x02;
// Android-specific extension.
private static final int EGL_RECORDABLE_ANDROID = 0x3142;
private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
private EGLConfig mEGLConfig = null;
private int mGlVersion = -1;
/**
* Prepares EGL display and context.
* <p>
* Equivalent to EglCore(null, 0).
*/
public EglCore() {
this(null, 0);
}
/**
* Prepares EGL display and context.
* <p>
* @param sharedContext The context to share, or null if sharing is not desired.
* @param flags Configuration bit flags, e.g. FLAG_RECORDABLE.
*/
public EglCore(EGLContext sharedContext, int flags) {
if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
throw new RuntimeException("EGL already set up");
}
if (sharedContext == null) {
sharedContext = EGL14.EGL_NO_CONTEXT;
}
mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
throw new RuntimeException("unable to get EGL14 display");
}
int[] version = new int[2];
if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
mEGLDisplay = null;
throw new RuntimeException("unable to initialize EGL14");
}
// Try to get a GLES3 context, if requested.
if ((flags & FLAG_TRY_GLES3) != 0) {
//Log.d(TAG, "Trying GLES 3");
EGLConfig config = getConfig(flags, 3);
if (config != null) {
int[] attrib3_list = {
EGL14.EGL_CONTEXT_CLIENT_VERSION, 3,
EGL14.EGL_NONE
};
EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext,
attrib3_list, 0);
if (EGL14.eglGetError() == EGL14.EGL_SUCCESS) {
//Log.d(TAG, "Got GLES 3 config");
mEGLConfig = config;
mEGLContext = context;
mGlVersion = 3;
}
}
}
if (mEGLContext == EGL14.EGL_NO_CONTEXT) { // GLES 2 only, or GLES 3 attempt failed
//Log.d(TAG, "Trying GLES 2");
EGLConfig config = getConfig(flags, 2);
if (config == null) {
throw new RuntimeException("Unable to find a suitable EGLConfig");
}
int[] attrib2_list = {
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
EGL14.EGL_NONE
};
EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext,
attrib2_list, 0);
checkEglError("eglCreateContext");
mEGLConfig = config;
mEGLContext = context;
mGlVersion = 2;
}
// Confirm with query.
int[] values = new int[1];
EGL14.eglQueryContext(mEGLDisplay, mEGLContext, EGL14.EGL_CONTEXT_CLIENT_VERSION,
values, 0);
// Log.d(TAG, "EGLContext created, client version " + values[0]);
}
/**
* Finds a suitable EGLConfig.
*
* @param flags Bit flags from constructor.
* @param version Must be 2 or 3.
*/
private EGLConfig getConfig(int flags, int version) {
int renderableType = EGL14.EGL_OPENGL_ES2_BIT;
if (version >= 3) {
renderableType |= EGLExt.EGL_OPENGL_ES3_BIT_KHR;
}
// The actual surface is generally RGBA or RGBX, so situationally omitting alpha
// doesn't really help. It can also lead to a huge performance hit on glReadPixels()
// when reading into a GL_RGBA buffer.
int[] attribList = {
EGL14.EGL_RED_SIZE, 8,
EGL14.EGL_GREEN_SIZE, 8,
EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_ALPHA_SIZE, 8,
//EGL14.EGL_DEPTH_SIZE, 16,
//EGL14.EGL_STENCIL_SIZE, 8,
EGL14.EGL_RENDERABLE_TYPE, renderableType,
EGL14.EGL_NONE, 0, // placeholder for recordable [@-3]
EGL14.EGL_NONE
};
if ((flags & FLAG_RECORDABLE) != 0) {
attribList[attribList.length - 3] = EGL_RECORDABLE_ANDROID;
attribList[attribList.length - 2] = 1;
}
EGLConfig[] configs = new EGLConfig[1];
int[] numConfigs = new int[1];
if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0,
configs.length, numConfigs, 0)) {
Log.w(TAG, "unable to find RGB8888 / " + version + " EGLConfig");
return null;
}
return configs[0];
}
/**
* Discards all resources held by this class, notably the EGL context. This must be
* called from the thread where the context was created.
* <p>
* On completion, no context will be current.
*/
public void release() {
if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
// Android is unusual in that it uses a reference-counted EGLDisplay. So for
// every eglInitialize() we need an eglTerminate().
EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
EGL14.EGL_NO_CONTEXT);
EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
EGL14.eglReleaseThread();
EGL14.eglTerminate(mEGLDisplay);
}
mEGLDisplay = EGL14.EGL_NO_DISPLAY;
mEGLContext = EGL14.EGL_NO_CONTEXT;
mEGLConfig = null;
}
@Override
protected void finalize() throws Throwable {
try {
if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
// We're limited here -- finalizers don't run on the thread that holds
// the EGL state, so if a surface or context is still current on another
// thread we can't fully release it here. Exceptions thrown from here
// are quietly discarded. Complain in the log file.
Log.w(TAG, "WARNING: EglCore was not explicitly released! " +
"State may be leaked");
release();
}
} finally {
super.finalize();
}
}
/**
* Destroys the specified surface. Note the EGLSurface won't actually be destroyed if it's
* still current in a context.
*/
public void releaseSurface(EGLSurface eglSurface) {
EGL14.eglDestroySurface(mEGLDisplay, eglSurface);
}
/**
* Creates an EGL surface associated with a Surface.
* <p>
* If this is destined for MediaCodec, the EGLConfig should have the "recordable" attribute.
*/
public EGLSurface createWindowSurface(Object surface) {
if (!(surface instanceof Surface) && !(surface instanceof SurfaceTexture)) {
throw new RuntimeException("invalid surface: " + surface);
}
// Create a window surface, and attach it to the Surface we received.
int[] surfaceAttribs = {
EGL14.EGL_NONE
};
EGLSurface eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, surface,
surfaceAttribs, 0);
checkEglError("eglCreateWindowSurface");
if (eglSurface == null) {
throw new RuntimeException("surface was null");
}
return eglSurface;
}
/**
* Creates an EGL surface associated with an offscreen buffer.
*/
public EGLSurface createOffscreenSurface(int width, int height) {
int[] surfaceAttribs = {
EGL14.EGL_WIDTH, width,
EGL14.EGL_HEIGHT, height,
EGL14.EGL_NONE
};
EGLSurface eglSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig,
surfaceAttribs, 0);
checkEglError("eglCreatePbufferSurface");
if (eglSurface == null) {
throw new RuntimeException("surface was null");
}
return eglSurface;
}
/**
* Makes our EGL context current, using the supplied surface for both "draw" and "read".
*/
public void makeCurrent(EGLSurface eglSurface) {
if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
// called makeCurrent() before create?
// Log.d(TAG, "NOTE: makeCurrent w/o display");
}
if (!EGL14.eglMakeCurrent(mEGLDisplay, eglSurface, eglSurface, mEGLContext)) {
throw new RuntimeException("eglMakeCurrent failed");
}
}
/**
* Makes our EGL context current, using the supplied "draw" and "read" surfaces.
*/
public void makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) {
if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
// called makeCurrent() before create?
Log.d(TAG, "NOTE: makeCurrent w/o display");
}
if (!EGL14.eglMakeCurrent(mEGLDisplay, drawSurface, readSurface, mEGLContext)) {
throw new RuntimeException("eglMakeCurrent(draw,read) failed");
}
}
/**
* Makes no context current.
*/
public void makeNothingCurrent() {
if (!EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
EGL14.EGL_NO_CONTEXT)) {
throw new RuntimeException("eglMakeCurrent failed");
}
}
/**
* Calls eglSwapBuffers. Use this to "publish" the current frame.
*
* @return false on failure
*/
public boolean swapBuffers(EGLSurface eglSurface) {
return EGL14.eglSwapBuffers(mEGLDisplay, eglSurface);
}
/**
* Sends the presentation time stamp to EGL. Time is expressed in nanoseconds.
* https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_presentation_time.txt
*/
public void setPresentationTime(EGLSurface eglSurface, long nsecs) {
EGLExt.eglPresentationTimeANDROID(mEGLDisplay, eglSurface, nsecs);
}
/**
* Returns true if our context and the specified surface are current.
*/
public boolean isCurrent(EGLSurface eglSurface) {
return mEGLContext.equals(EGL14.eglGetCurrentContext()) &&
eglSurface.equals(EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW));
}
/**
* Performs a simple surface query.
*/
public int querySurface(EGLSurface eglSurface, int what) {
int[] value = new int[1];
EGL14.eglQuerySurface(mEGLDisplay, eglSurface, what, value, 0);
return value[0];
}
/**
* Queries a string value.
*/
public String queryString(int what) {
return EGL14.eglQueryString(mEGLDisplay, what);
}
/**
* Returns the GLES version this context is configured for (currently 2 or 3).
*/
public int getGlVersion() {
return mGlVersion;
}
/**
* Checks for EGL errors. Throws an exception if an error has been raised.
*/
private void checkEglError(String msg) {
int error;
if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error));
}
}
}

@ -1,103 +0,0 @@
package com.otaliastudios.cameraview.internal.egl;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.filter.Filter;
import com.otaliastudios.cameraview.filter.NoFilter;
import com.otaliastudios.cameraview.internal.GlUtils;
public class EglViewport {
private final static CameraLogger LOG = CameraLogger.create(EglViewport.class.getSimpleName());
private int mProgramHandle = -1;
private int mTextureTarget;
private int mTextureUnit;
private Filter mFilter;
private Filter mPendingFilter;
public EglViewport() {
this(new NoFilter());
}
public EglViewport(@NonNull Filter filter) {
mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES;
mTextureUnit = GLES20.GL_TEXTURE0;
mFilter = filter;
createProgram();
}
private void createProgram() {
mProgramHandle = GlUtils.createProgram(mFilter.getVertexShader(),
mFilter.getFragmentShader());
mFilter.onCreate(mProgramHandle);
}
public void release() {
if (mProgramHandle != -1) {
mFilter.onDestroy();
GLES20.glDeleteProgram(mProgramHandle);
mProgramHandle = -1;
}
}
public int createTexture() {
int[] textures = new int[1];
GLES20.glGenTextures(1, textures, 0);
GlUtils.checkError("glGenTextures");
int texId = textures[0];
GLES20.glActiveTexture(mTextureUnit);
GLES20.glBindTexture(mTextureTarget, texId);
GlUtils.checkError("glBindTexture " + texId);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_NEAREST);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
GLES20.GL_CLAMP_TO_EDGE);
GlUtils.checkError("glTexParameter");
return texId;
}
public void setFilter(@NonNull Filter filter) {
// TODO see if this is needed. If setFilter is always called from the correct GL thread,
// we don't need to wait for a new draw call (which might not even happen).
mPendingFilter = filter;
}
public void drawFrame(long timestampUs, int textureId, float[] textureMatrix) {
if (mPendingFilter != null) {
release();
mFilter = mPendingFilter;
mPendingFilter = null;
createProgram();
}
GlUtils.checkError("draw start");
// Select the program and the active texture.
GLES20.glUseProgram(mProgramHandle);
GlUtils.checkError("glUseProgram");
GLES20.glActiveTexture(mTextureUnit);
GLES20.glBindTexture(mTextureTarget, textureId);
// Draw.
mFilter.draw(timestampUs, textureMatrix);
// Release.
GLES20.glBindTexture(mTextureTarget, 0);
GLES20.glUseProgram(0);
}
}

@ -1,80 +0,0 @@
/*
* Copyright 2013 Google Inc. All rights reserved.
*
* 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.otaliastudios.cameraview.internal.egl;
import android.graphics.SurfaceTexture;
import android.os.Build;
import androidx.annotation.RequiresApi;
import android.view.Surface;
/**
* Recordable EGL window surface.
* <p>
* It's good practice to explicitly release() the surface, preferably from a "finally" block.
*/
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class EglWindowSurface extends EglBaseSurface {
private Surface mSurface;
private boolean mReleaseSurface;
/**
* Associates an EGL surface with the native window surface.
* <p>
* Set releaseSurface to true if you want the Surface to be released when release() is
* called. This is convenient, but can interfere with framework classes that expect to
* manage the Surface themselves (e.g. if you release a SurfaceView's Surface, the
* surfaceDestroyed() callback won't fire).
*/
public EglWindowSurface(EglCore eglCore, Surface surface, boolean releaseSurface) {
super(eglCore);
createWindowSurface(surface);
mSurface = surface;
mReleaseSurface = releaseSurface;
}
/**
* Associates an EGL surface with the SurfaceTexture.
*/
public EglWindowSurface(EglCore eglCore, SurfaceTexture surfaceTexture) {
super(eglCore);
createWindowSurface(surfaceTexture);
}
/**
* Associates an EGL surface with the Surface.
*/
public EglWindowSurface(EglCore eglCore, Surface surface) {
super(eglCore);
createWindowSurface(surface);
}
/**
* Releases any resources associated with the EGL surface (and, if configured to do so,
* with the Surface as well).
* <p>
* Does not require that the surface's EGL context be current.
*/
public void release() {
releaseEglSurface();
if (mSurface != null) {
if (mReleaseSurface) {
mSurface.release();
}
mSurface = null;
}
}
}

@ -12,8 +12,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.internal.GlTextureDrawer;
import com.otaliastudios.cameraview.internal.Issue514Workaround; import com.otaliastudios.cameraview.internal.Issue514Workaround;
import com.otaliastudios.cameraview.internal.egl.EglViewport;
import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.Size;
import java.nio.Buffer; import java.nio.Buffer;
@ -27,7 +27,7 @@ import java.nio.Buffer;
* - Renders this into the current EGL window: {@link #render(long)} * - Renders this into the current EGL window: {@link #render(long)}
* - Applies the {@link Issue514Workaround} the correct way * - Applies the {@link Issue514Workaround} the correct way
* *
* In the future we might want to use a different approach than {@link EglViewport}, * In the future we might want to use a different approach than {@link GlTextureDrawer},
* {@link SurfaceTexture} and {@link GLES11Ext#GL_TEXTURE_EXTERNAL_OES}, * {@link SurfaceTexture} and {@link GLES11Ext#GL_TEXTURE_EXTERNAL_OES},
* for example by using a regular {@link GLES20#GL_TEXTURE_2D} that might * for example by using a regular {@link GLES20#GL_TEXTURE_2D} that might
* be filled through {@link GLES20#glTexImage2D(int, int, int, int, int, int, int, int, Buffer)}. * be filled through {@link GLES20#glTexImage2D(int, int, int, int, int, int, int, int, Buffer)}.
@ -40,22 +40,19 @@ public class OverlayDrawer {
private static final CameraLogger LOG = CameraLogger.create(TAG); private static final CameraLogger LOG = CameraLogger.create(TAG);
private Overlay mOverlay; private Overlay mOverlay;
@VisibleForTesting int mTextureId;
private SurfaceTexture mSurfaceTexture; private SurfaceTexture mSurfaceTexture;
private Surface mSurface; private Surface mSurface;
private float[] mTransform = new float[16]; @VisibleForTesting GlTextureDrawer mTextureDrawer;
@VisibleForTesting EglViewport mViewport;
private Issue514Workaround mIssue514Workaround; private Issue514Workaround mIssue514Workaround;
private final Object mIssue514WorkaroundLock = new Object(); private final Object mIssue514WorkaroundLock = new Object();
public OverlayDrawer(@NonNull Overlay overlay, @NonNull Size size) { public OverlayDrawer(@NonNull Overlay overlay, @NonNull Size size) {
mOverlay = overlay; mOverlay = overlay;
mViewport = new EglViewport(); mTextureDrawer = new GlTextureDrawer();
mTextureId = mViewport.createTexture(); mSurfaceTexture = new SurfaceTexture(mTextureDrawer.getTexture().getId());
mSurfaceTexture = new SurfaceTexture(mTextureId);
mSurfaceTexture.setDefaultBufferSize(size.getWidth(), size.getHeight()); mSurfaceTexture.setDefaultBufferSize(size.getWidth(), size.getHeight());
mSurface = new Surface(mSurfaceTexture); mSurface = new Surface(mSurfaceTexture);
mIssue514Workaround = new Issue514Workaround(mTextureId); mIssue514Workaround = new Issue514Workaround(mTextureDrawer.getTexture().getId());
} }
/** /**
@ -77,7 +74,7 @@ public class OverlayDrawer {
mIssue514Workaround.beforeOverlayUpdateTexImage(); mIssue514Workaround.beforeOverlayUpdateTexImage();
mSurfaceTexture.updateTexImage(); mSurfaceTexture.updateTexImage();
} }
mSurfaceTexture.getTransformMatrix(mTransform); mSurfaceTexture.getTransformMatrix(mTextureDrawer.getTextureTransform());
} }
/** /**
@ -86,7 +83,7 @@ public class OverlayDrawer {
* @return the transform matrix * @return the transform matrix
*/ */
public float[] getTransform() { public float[] getTransform() {
return mTransform; return mTextureDrawer.getTextureTransform();
} }
/** /**
@ -105,7 +102,7 @@ public class OverlayDrawer {
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
synchronized (mIssue514WorkaroundLock) { synchronized (mIssue514WorkaroundLock) {
mViewport.drawFrame(timestampUs, mTextureId, mTransform); mTextureDrawer.draw(timestampUs);
} }
} }
@ -125,9 +122,9 @@ public class OverlayDrawer {
mSurface.release(); mSurface.release();
mSurface = null; mSurface = null;
} }
if (mViewport != null) { if (mTextureDrawer != null) {
mViewport.release(); mTextureDrawer.release();
mViewport = null; mTextureDrawer = null;
} }
} }
} }

@ -4,10 +4,9 @@ import android.hardware.Camera;
import com.otaliastudios.cameraview.PictureResult; import com.otaliastudios.cameraview.PictureResult;
import com.otaliastudios.cameraview.engine.Camera1Engine; import com.otaliastudios.cameraview.engine.Camera1Engine;
import com.otaliastudios.cameraview.internal.utils.ExifHelper; import com.otaliastudios.cameraview.internal.ExifHelper;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.exifinterface.media.ExifInterface; import androidx.exifinterface.media.ExifInterface;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;

@ -15,8 +15,8 @@ import com.otaliastudios.cameraview.engine.Camera2Engine;
import com.otaliastudios.cameraview.engine.action.Action; import com.otaliastudios.cameraview.engine.action.Action;
import com.otaliastudios.cameraview.engine.action.ActionHolder; import com.otaliastudios.cameraview.engine.action.ActionHolder;
import com.otaliastudios.cameraview.engine.action.BaseAction; import com.otaliastudios.cameraview.engine.action.BaseAction;
import com.otaliastudios.cameraview.internal.utils.ExifHelper; import com.otaliastudios.cameraview.internal.ExifHelper;
import com.otaliastudios.cameraview.internal.utils.WorkerHandler; import com.otaliastudios.cameraview.internal.WorkerHandler;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;

@ -7,9 +7,9 @@ import android.hardware.Camera;
import com.otaliastudios.cameraview.PictureResult; import com.otaliastudios.cameraview.PictureResult;
import com.otaliastudios.cameraview.engine.Camera1Engine; import com.otaliastudios.cameraview.engine.Camera1Engine;
import com.otaliastudios.cameraview.engine.offset.Reference; import com.otaliastudios.cameraview.engine.offset.Reference;
import com.otaliastudios.cameraview.internal.utils.CropHelper; import com.otaliastudios.cameraview.internal.CropHelper;
import com.otaliastudios.cameraview.internal.utils.RotationHelper; import com.otaliastudios.cameraview.internal.RotationHelper;
import com.otaliastudios.cameraview.internal.utils.WorkerHandler; import com.otaliastudios.cameraview.internal.WorkerHandler;
import com.otaliastudios.cameraview.size.AspectRatio; import com.otaliastudios.cameraview.size.AspectRatio;
import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.Size;

@ -10,17 +10,14 @@ import android.opengl.Matrix;
import android.os.Build; import android.os.Build;
import com.otaliastudios.cameraview.PictureResult; import com.otaliastudios.cameraview.PictureResult;
import com.otaliastudios.cameraview.internal.egl.EglBaseSurface; import com.otaliastudios.cameraview.internal.GlTextureDrawer;
import com.otaliastudios.cameraview.overlay.Overlay; import com.otaliastudios.cameraview.overlay.Overlay;
import com.otaliastudios.cameraview.controls.Facing; import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.engine.CameraEngine; import com.otaliastudios.cameraview.engine.CameraEngine;
import com.otaliastudios.cameraview.engine.offset.Axis; import com.otaliastudios.cameraview.engine.offset.Axis;
import com.otaliastudios.cameraview.engine.offset.Reference; import com.otaliastudios.cameraview.engine.offset.Reference;
import com.otaliastudios.cameraview.internal.egl.EglCore; import com.otaliastudios.cameraview.internal.CropHelper;
import com.otaliastudios.cameraview.internal.egl.EglViewport; import com.otaliastudios.cameraview.internal.WorkerHandler;
import com.otaliastudios.cameraview.internal.egl.EglWindowSurface;
import com.otaliastudios.cameraview.internal.utils.CropHelper;
import com.otaliastudios.cameraview.internal.utils.WorkerHandler;
import com.otaliastudios.cameraview.overlay.OverlayDrawer; import com.otaliastudios.cameraview.overlay.OverlayDrawer;
import com.otaliastudios.cameraview.preview.GlCameraPreview; import com.otaliastudios.cameraview.preview.GlCameraPreview;
import com.otaliastudios.cameraview.preview.RendererFrameCallback; import com.otaliastudios.cameraview.preview.RendererFrameCallback;
@ -28,6 +25,9 @@ import com.otaliastudios.cameraview.preview.RendererThread;
import com.otaliastudios.cameraview.filter.Filter; import com.otaliastudios.cameraview.filter.Filter;
import com.otaliastudios.cameraview.size.AspectRatio; import com.otaliastudios.cameraview.size.AspectRatio;
import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.Size;
import com.otaliastudios.opengl.core.EglCore;
import com.otaliastudios.opengl.surface.EglSurface;
import com.otaliastudios.opengl.surface.EglWindowSurface;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread; import androidx.annotation.WorkerThread;
@ -45,7 +45,7 @@ import android.view.Surface;
* - We move to another thread, and create a new EGL surface for that EGL context. * - We move to another thread, and create a new EGL surface for that EGL context.
* - We make this new surface current, and re-draw the textureId on it * - We make this new surface current, and re-draw the textureId on it
* - [Optional: fill the overlayTextureId and draw it on the same surface] * - [Optional: fill the overlayTextureId and draw it on the same surface]
* - We use glReadPixels (through {@link EglBaseSurface#saveFrameTo(Bitmap.CompressFormat)}) * - We use glReadPixels (through {@link EglSurface#toByteArray(Bitmap.CompressFormat)})
* and save to file. * and save to file.
* *
* We create a new EGL surface and redraw the frame because: * We create a new EGL surface and redraw the frame because:
@ -62,12 +62,7 @@ public class SnapshotGlPictureRecorder extends SnapshotPictureRecorder {
private Overlay mOverlay; private Overlay mOverlay;
private boolean mHasOverlay; private boolean mHasOverlay;
private OverlayDrawer mOverlayDrawer; private OverlayDrawer mOverlayDrawer;
private GlTextureDrawer mTextureDrawer;
private int mTextureId;
private float[] mTransform;
private EglViewport mViewport;
public SnapshotGlPictureRecorder( public SnapshotGlPictureRecorder(
@NonNull PictureResult.Stub stub, @NonNull PictureResult.Stub stub,
@ -114,14 +109,10 @@ public class SnapshotGlPictureRecorder extends SnapshotPictureRecorder {
@RendererThread @RendererThread
@TargetApi(Build.VERSION_CODES.KITKAT) @TargetApi(Build.VERSION_CODES.KITKAT)
protected void onRendererTextureCreated(int textureId) { protected void onRendererTextureCreated(int textureId) {
mTextureId = textureId; mTextureDrawer = new GlTextureDrawer(textureId);
mViewport = new EglViewport();
// Need to crop the size. // Need to crop the size.
Rect crop = CropHelper.computeCrop(mResult.size, mOutputRatio); Rect crop = CropHelper.computeCrop(mResult.size, mOutputRatio);
mResult.size = new Size(crop.width(), crop.height()); mResult.size = new Size(crop.width(), crop.height());
mTransform = new float[16];
Matrix.setIdentityM(mTransform, 0);
if (mHasOverlay) { if (mHasOverlay) {
mOverlayDrawer = new OverlayDrawer(mOverlay, mResult.size); mOverlayDrawer = new OverlayDrawer(mOverlay, mResult.size);
} }
@ -131,7 +122,7 @@ public class SnapshotGlPictureRecorder extends SnapshotPictureRecorder {
@RendererThread @RendererThread
@TargetApi(Build.VERSION_CODES.KITKAT) @TargetApi(Build.VERSION_CODES.KITKAT)
protected void onRendererFilterChanged(@NonNull Filter filter) { protected void onRendererFilterChanged(@NonNull Filter filter) {
mViewport.setFilter(filter.copy()); mTextureDrawer.setFilter(filter.copy());
} }
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
@ -194,8 +185,9 @@ public class SnapshotGlPictureRecorder extends SnapshotPictureRecorder {
// 1. Create an EGL surface // 1. Create an EGL surface
final EglCore core = new EglCore(eglContext, EglCore.FLAG_RECORDABLE); final EglCore core = new EglCore(eglContext, EglCore.FLAG_RECORDABLE);
final EglBaseSurface eglSurface = new EglWindowSurface(core, fakeOutputSurface); final EglSurface eglSurface = new EglWindowSurface(core, fakeOutputSurface);
eglSurface.makeCurrent(); eglSurface.makeCurrent();
final float[] transform = mTextureDrawer.getTextureTransform();
// 2. Apply scale and crop // 2. Apply scale and crop
boolean flip = mEngine.getAngles().flip(Reference.VIEW, Reference.SENSOR); boolean flip = mEngine.getAngles().flip(Reference.VIEW, Reference.SENSOR);
@ -203,17 +195,17 @@ public class SnapshotGlPictureRecorder extends SnapshotPictureRecorder {
float realScaleY = flip ? scaleX : scaleY; float realScaleY = flip ? scaleX : scaleY;
float scaleTranslX = (1F - realScaleX) / 2F; float scaleTranslX = (1F - realScaleX) / 2F;
float scaleTranslY = (1F - realScaleY) / 2F; float scaleTranslY = (1F - realScaleY) / 2F;
Matrix.translateM(mTransform, 0, scaleTranslX, scaleTranslY, 0); Matrix.translateM(transform, 0, scaleTranslX, scaleTranslY, 0);
Matrix.scaleM(mTransform, 0, realScaleX, realScaleY, 1); Matrix.scaleM(transform, 0, realScaleX, realScaleY, 1);
// 3. Apply rotation and flip // 3. Apply rotation and flip
Matrix.translateM(mTransform, 0, 0.5F, 0.5F, 0); // Go back to 0,0 Matrix.translateM(transform, 0, 0.5F, 0.5F, 0); // Go back to 0,0
Matrix.rotateM(mTransform, 0, -mResult.rotation, 0, 0, 1); // Rotate (not sure why we need the minus) Matrix.rotateM(transform, 0, -mResult.rotation, 0, 0, 1); // Rotate (not sure why we need the minus)
mResult.rotation = 0; mResult.rotation = 0;
if (mResult.facing == Facing.FRONT) { // 5. Flip horizontally for front camera if (mResult.facing == Facing.FRONT) { // 5. Flip horizontally for front camera
Matrix.scaleM(mTransform, 0, -1, 1, 1); Matrix.scaleM(transform, 0, -1, 1, 1);
} }
Matrix.translateM(mTransform, 0, -0.5F, -0.5F, 0); // Go back to old position Matrix.translateM(transform, 0, -0.5F, -0.5F, 0); // Go back to old position
// 4. Do pretty much the same for overlays // 4. Do pretty much the same for overlays
if (mHasOverlay) { if (mHasOverlay) {
@ -232,13 +224,13 @@ public class SnapshotGlPictureRecorder extends SnapshotPictureRecorder {
// 5. Draw and save // 5. Draw and save
long timestampUs = surfaceTexture.getTimestamp() / 1000L; long timestampUs = surfaceTexture.getTimestamp() / 1000L;
LOG.i("takeFrame:", "timestampUs:", timestampUs); LOG.i("takeFrame:", "timestampUs:", timestampUs);
mViewport.drawFrame(timestampUs, mTextureId, mTransform); mTextureDrawer.draw(timestampUs);
if (mHasOverlay) mOverlayDrawer.render(timestampUs); if (mHasOverlay) mOverlayDrawer.render(timestampUs);
mResult.data = eglSurface.saveFrameTo(Bitmap.CompressFormat.JPEG); mResult.data = eglSurface.toByteArray(Bitmap.CompressFormat.JPEG);
// 6. Cleanup // 6. Cleanup
eglSurface.releaseEglSurface(); eglSurface.release();
mViewport.release(); mTextureDrawer.release();
fakeOutputSurface.release(); fakeOutputSurface.release();
if (mHasOverlay) mOverlayDrawer.release(); if (mHasOverlay) mOverlayDrawer.release();
core.release(); core.release();

@ -14,7 +14,7 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.otaliastudios.cameraview.R; import com.otaliastudios.cameraview.R;
import com.otaliastudios.cameraview.internal.egl.EglViewport; import com.otaliastudios.cameraview.internal.GlTextureDrawer;
import com.otaliastudios.cameraview.filter.Filter; import com.otaliastudios.cameraview.filter.Filter;
import com.otaliastudios.cameraview.filter.NoFilter; import com.otaliastudios.cameraview.filter.NoFilter;
import com.otaliastudios.cameraview.size.AspectRatio; import com.otaliastudios.cameraview.size.AspectRatio;
@ -63,10 +63,8 @@ import javax.microedition.khronos.opengles.GL10;
public class GlCameraPreview extends FilterCameraPreview<GLSurfaceView, SurfaceTexture> { public class GlCameraPreview extends FilterCameraPreview<GLSurfaceView, SurfaceTexture> {
private boolean mDispatched; private boolean mDispatched;
private final float[] mTransformMatrix = new float[16];
private int mOutputTextureId = 0;
private SurfaceTexture mInputSurfaceTexture; private SurfaceTexture mInputSurfaceTexture;
private EglViewport mOutputViewport; private GlTextureDrawer mOutputTextureDrawer;
// A synchronized set was not enough to avoid crashes, probably due to external classes // A synchronized set was not enough to avoid crashes, probably due to external classes
// removing the callback while this set is being iterated. CopyOnWriteArraySet solves this. // removing the callback while this set is being iterated. CopyOnWriteArraySet solves this.
private final Set<RendererFrameCallback> mRendererFrameCallbacks = new CopyOnWriteArraySet<>(); private final Set<RendererFrameCallback> mRendererFrameCallbacks = new CopyOnWriteArraySet<>();
@ -146,14 +144,15 @@ public class GlCameraPreview extends FilterCameraPreview<GLSurfaceView, SurfaceT
if (mCurrentFilter == null) { if (mCurrentFilter == null) {
mCurrentFilter = new NoFilter(); mCurrentFilter = new NoFilter();
} }
mOutputViewport = new EglViewport(mCurrentFilter); mOutputTextureDrawer = new GlTextureDrawer();
mOutputTextureId = mOutputViewport.createTexture(); mOutputTextureDrawer.setFilter(mCurrentFilter);
mInputSurfaceTexture = new SurfaceTexture(mOutputTextureId); final int textureId = mOutputTextureDrawer.getTexture().getId();
mInputSurfaceTexture = new SurfaceTexture(textureId);
getView().queueEvent(new Runnable() { getView().queueEvent(new Runnable() {
@Override @Override
public void run() { public void run() {
for (RendererFrameCallback callback : mRendererFrameCallbacks) { for (RendererFrameCallback callback : mRendererFrameCallbacks) {
callback.onRendererTextureCreated(mOutputTextureId); callback.onRendererTextureCreated(textureId);
} }
} }
}); });
@ -176,10 +175,9 @@ public class GlCameraPreview extends FilterCameraPreview<GLSurfaceView, SurfaceT
mInputSurfaceTexture.release(); mInputSurfaceTexture.release();
mInputSurfaceTexture = null; mInputSurfaceTexture = null;
} }
mOutputTextureId = 0; if (mOutputTextureDrawer != null) {
if (mOutputViewport != null) { mOutputTextureDrawer.release();
mOutputViewport.release(); mOutputTextureDrawer = null;
mOutputViewport = null;
} }
} }
@ -207,17 +205,17 @@ public class GlCameraPreview extends FilterCameraPreview<GLSurfaceView, SurfaceT
// Latch the latest frame. If there isn't anything new, // Latch the latest frame. If there isn't anything new,
// we'll just re-use whatever was there before. // we'll just re-use whatever was there before.
final float[] transform = mOutputTextureDrawer.getTextureTransform();
mInputSurfaceTexture.updateTexImage(); mInputSurfaceTexture.updateTexImage();
mInputSurfaceTexture.getTransformMatrix(mTransformMatrix); mInputSurfaceTexture.getTransformMatrix(transform);
// LOG.v("onDrawFrame:", "timestamp:", mInputSurfaceTexture.getTimestamp()); // LOG.v("onDrawFrame:", "timestamp:", mInputSurfaceTexture.getTimestamp());
// For Camera2, apply the draw rotation. // For Camera2, apply the draw rotation.
// See TextureCameraPreview.setDrawRotation() for info. // See TextureCameraPreview.setDrawRotation() for info.
if (mDrawRotation != 0) { if (mDrawRotation != 0) {
Matrix.translateM(mTransformMatrix, 0, 0.5F, 0.5F, 0); Matrix.translateM(transform, 0, 0.5F, 0.5F, 0);
Matrix.rotateM(mTransformMatrix, 0, mDrawRotation, 0, 0, 1); Matrix.rotateM(transform, 0, mDrawRotation, 0, 0, 1);
Matrix.translateM(mTransformMatrix, 0, -0.5F, -0.5F, 0); Matrix.translateM(transform, 0, -0.5F, -0.5F, 0);
} }
if (isCropping()) { if (isCropping()) {
@ -228,11 +226,11 @@ public class GlCameraPreview extends FilterCameraPreview<GLSurfaceView, SurfaceT
// of the preview (not the center one). // of the preview (not the center one).
float translX = (1F - mCropScaleX) / 2F; float translX = (1F - mCropScaleX) / 2F;
float translY = (1F - mCropScaleY) / 2F; float translY = (1F - mCropScaleY) / 2F;
Matrix.translateM(mTransformMatrix, 0, translX, translY, 0); Matrix.translateM(transform, 0, translX, translY, 0);
Matrix.scaleM(mTransformMatrix, 0, mCropScaleX, mCropScaleY, 1); Matrix.scaleM(transform, 0, mCropScaleX, mCropScaleY, 1);
} }
mOutputViewport.drawFrame(mInputSurfaceTexture.getTimestamp() / 1000L,
mOutputTextureId, mTransformMatrix); mOutputTextureDrawer.draw(mInputSurfaceTexture.getTimestamp() / 1000L);
for (RendererFrameCallback callback : mRendererFrameCallbacks) { for (RendererFrameCallback callback : mRendererFrameCallbacks) {
callback.onRendererFrame(mInputSurfaceTexture, mCropScaleX, mCropScaleY); callback.onRendererFrame(mInputSurfaceTexture, mCropScaleX, mCropScaleY);
} }
@ -301,7 +299,10 @@ public class GlCameraPreview extends FilterCameraPreview<GLSurfaceView, SurfaceT
@Override @Override
public void run() { public void run() {
mRendererFrameCallbacks.add(callback); mRendererFrameCallbacks.add(callback);
if (mOutputTextureId != 0) callback.onRendererTextureCreated(mOutputTextureId); if (mOutputTextureDrawer != null) {
int textureId = mOutputTextureDrawer.getTexture().getId();
callback.onRendererTextureCreated(textureId);
}
callback.onRendererFilterChanged(mCurrentFilter); callback.onRendererFilterChanged(mCurrentFilter);
} }
}); });
@ -322,7 +323,7 @@ public class GlCameraPreview extends FilterCameraPreview<GLSurfaceView, SurfaceT
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
protected int getTextureId() { protected int getTextureId() {
return mOutputTextureId; return mOutputTextureDrawer != null ? mOutputTextureDrawer.getTexture().getId() : -1;
} }
/** /**
@ -354,8 +355,8 @@ public class GlCameraPreview extends FilterCameraPreview<GLSurfaceView, SurfaceT
getView().queueEvent(new Runnable() { getView().queueEvent(new Runnable() {
@Override @Override
public void run() { public void run() {
if (mOutputViewport != null) { if (mOutputTextureDrawer != null) {
mOutputViewport.setFilter(filter); mOutputTextureDrawer.setFilter(filter);
} }
for (RendererFrameCallback callback : mRendererFrameCallbacks) { for (RendererFrameCallback callback : mRendererFrameCallbacks) {
callback.onRendererFilterChanged(filter); callback.onRendererFilterChanged(filter);

@ -6,7 +6,7 @@ import android.media.MediaRecorder;
import com.otaliastudios.cameraview.VideoResult; import com.otaliastudios.cameraview.VideoResult;
import com.otaliastudios.cameraview.engine.Camera1Engine; import com.otaliastudios.cameraview.engine.Camera1Engine;
import com.otaliastudios.cameraview.internal.utils.CamcorderProfiles; import com.otaliastudios.cameraview.internal.CamcorderProfiles;
import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.Size;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;

@ -12,7 +12,7 @@ import com.otaliastudios.cameraview.engine.action.Action;
import com.otaliastudios.cameraview.engine.action.ActionHolder; import com.otaliastudios.cameraview.engine.action.ActionHolder;
import com.otaliastudios.cameraview.engine.action.BaseAction; import com.otaliastudios.cameraview.engine.action.BaseAction;
import com.otaliastudios.cameraview.engine.action.CompletionCallback; import com.otaliastudios.cameraview.engine.action.CompletionCallback;
import com.otaliastudios.cameraview.internal.utils.CamcorderProfiles; import com.otaliastudios.cameraview.internal.CamcorderProfiles;
import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.Size;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;

@ -8,7 +8,7 @@ import com.otaliastudios.cameraview.VideoResult;
import com.otaliastudios.cameraview.controls.Audio; import com.otaliastudios.cameraview.controls.Audio;
import com.otaliastudios.cameraview.controls.VideoCodec; import com.otaliastudios.cameraview.controls.VideoCodec;
import com.otaliastudios.cameraview.internal.DeviceEncoders; import com.otaliastudios.cameraview.internal.DeviceEncoders;
import com.otaliastudios.cameraview.internal.utils.CamcorderProfiles; import com.otaliastudios.cameraview.internal.CamcorderProfiles;
import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.Size;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;

@ -1,6 +1,6 @@
package com.otaliastudios.cameraview.video.encoding; package com.otaliastudios.cameraview.video.encoding;
import com.otaliastudios.cameraview.internal.utils.Pool; import com.otaliastudios.cameraview.internal.Pool;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;

@ -1,6 +1,6 @@
package com.otaliastudios.cameraview.video.encoding; package com.otaliastudios.cameraview.video.encoding;
import com.otaliastudios.cameraview.internal.utils.Pool; import com.otaliastudios.cameraview.internal.Pool;
/** /**
* A simple {@link Pool(int, Factory)} implementation for input buffers. * A simple {@link Pool(int, Factory)} implementation for input buffers.

@ -11,7 +11,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.internal.utils.WorkerHandler; import com.otaliastudios.cameraview.internal.WorkerHandler;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.HashMap; import java.util.HashMap;

@ -4,10 +4,9 @@ import android.annotation.SuppressLint;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.media.MediaMuxer; import android.media.MediaMuxer;
import android.os.Build; import android.os.Build;
import android.text.format.DateFormat;
import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.internal.utils.WorkerHandler; import com.otaliastudios.cameraview.internal.WorkerHandler;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;

@ -3,7 +3,7 @@ package com.otaliastudios.cameraview.video.encoding;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.os.Build; import android.os.Build;
import com.otaliastudios.cameraview.internal.utils.Pool; import com.otaliastudios.cameraview.internal.Pool;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;

@ -10,10 +10,10 @@ import androidx.annotation.RequiresApi;
import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.filter.Filter; import com.otaliastudios.cameraview.filter.Filter;
import com.otaliastudios.cameraview.internal.egl.EglCore; import com.otaliastudios.cameraview.internal.GlTextureDrawer;
import com.otaliastudios.cameraview.internal.egl.EglViewport; import com.otaliastudios.cameraview.internal.Pool;
import com.otaliastudios.cameraview.internal.egl.EglWindowSurface; import com.otaliastudios.opengl.core.EglCore;
import com.otaliastudios.cameraview.internal.utils.Pool; import com.otaliastudios.opengl.surface.EglWindowSurface;
/** /**
* Default implementation for video encoding. * Default implementation for video encoding.
@ -30,7 +30,7 @@ public class TextureMediaEncoder extends VideoMediaEncoder<TextureConfig> {
private int mTransformRotation; private int mTransformRotation;
private EglCore mEglCore; private EglCore mEglCore;
private EglWindowSurface mWindow; private EglWindowSurface mWindow;
private EglViewport mViewport; private GlTextureDrawer mDrawer;
private Pool<Frame> mFramePool = new Pool<>(Integer.MAX_VALUE, new Pool.Factory<Frame>() { private Pool<Frame> mFramePool = new Pool<>(Integer.MAX_VALUE, new Pool.Factory<Frame>() {
@Override @Override
public Frame create() { public Frame create() {
@ -99,7 +99,7 @@ public class TextureMediaEncoder extends VideoMediaEncoder<TextureConfig> {
mEglCore = new EglCore(mConfig.eglContext, EglCore.FLAG_RECORDABLE); mEglCore = new EglCore(mConfig.eglContext, EglCore.FLAG_RECORDABLE);
mWindow = new EglWindowSurface(mEglCore, mSurface, true); mWindow = new EglWindowSurface(mEglCore, mSurface, true);
mWindow.makeCurrent(); mWindow.makeCurrent();
mViewport = new EglViewport(); mDrawer = new GlTextureDrawer(mConfig.textureId);
} }
/** /**
@ -149,7 +149,7 @@ public class TextureMediaEncoder extends VideoMediaEncoder<TextureConfig> {
} }
private void onFilter(@NonNull Filter filter) { private void onFilter(@NonNull Filter filter) {
mViewport.setFilter(filter); mDrawer.setFilter(filter);
} }
private void onFrame(@NonNull Frame frame) { private void onFrame(@NonNull Frame frame) {
@ -229,7 +229,8 @@ public class TextureMediaEncoder extends VideoMediaEncoder<TextureConfig> {
"hasReachedMaxLength:", hasReachedMaxLength(), "hasReachedMaxLength:", hasReachedMaxLength(),
"thread:", Thread.currentThread(), "thread:", Thread.currentThread(),
"- gl rendering."); "- gl rendering.");
mViewport.drawFrame(frame.timestampUs(), mConfig.textureId, transform); mDrawer.setTextureTransform(transform);
mDrawer.draw(frame.timestampUs());
if (mConfig.hasOverlay()) { if (mConfig.hasOverlay()) {
mConfig.overlayDrawer.render(frame.timestampUs()); mConfig.overlayDrawer.render(frame.timestampUs());
} }
@ -252,9 +253,9 @@ public class TextureMediaEncoder extends VideoMediaEncoder<TextureConfig> {
mWindow.release(); mWindow.release();
mWindow = null; mWindow = null;
} }
if (mViewport != null) { if (mDrawer != null) {
mViewport.release(); mDrawer.release();
mViewport = null; mDrawer = null;
} }
if (mEglCore != null) { if (mEglCore != null) {
mEglCore.release(); mEglCore.release();

@ -1,8 +1,10 @@
package com.otaliastudios.cameraview.internal.utils; package com.otaliastudios.cameraview.internal;
import androidx.exifinterface.media.ExifInterface; import androidx.exifinterface.media.ExifInterface;
import com.otaliastudios.cameraview.internal.ExifHelper;
import org.junit.Test; import org.junit.Test;
import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNotNull;

@ -1,6 +1,8 @@
package com.otaliastudios.cameraview.internal.utils; package com.otaliastudios.cameraview.internal;
import com.otaliastudios.cameraview.internal.Pool;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;

@ -48,7 +48,7 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
private final static CameraLogger LOG = CameraLogger.create("DemoApp"); private final static CameraLogger LOG = CameraLogger.create("DemoApp");
private final static boolean USE_FRAME_PROCESSOR = true; private final static boolean USE_FRAME_PROCESSOR = true;
private final static boolean DECODE_BITMAP = true; private final static boolean DECODE_BITMAP = false;
private CameraView camera; private CameraView camera;
private ViewGroup controlPanel; private ViewGroup controlPanel;
@ -76,7 +76,7 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
long newTime = frame.getTime(); long newTime = frame.getTime();
long delay = newTime - lastTime; long delay = newTime - lastTime;
lastTime = newTime; lastTime = newTime;
LOG.e("Frame delayMillis:", delay, "FPS:", 1000 / delay); LOG.v("Frame delayMillis:", delay, "FPS:", 1000 / delay);
if (DECODE_BITMAP) { if (DECODE_BITMAP) {
if (frame.getFormat() == ImageFormat.NV21 if (frame.getFormat() == ImageFormat.NV21
&& frame.getDataClass() == byte[].class) { && frame.getDataClass() == byte[].class) {

Loading…
Cancel
Save