diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/IntegrationTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/IntegrationTest.java index 19ec596c..f37f17a5 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/IntegrationTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/IntegrationTest.java @@ -12,7 +12,6 @@ import android.support.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -481,11 +480,11 @@ public class IntegrationTest extends BaseTest { Size size = camera.getPictureSize(); camera.takePicture(); PictureResult result = waitForPicture(true); - Bitmap bitmap = CameraUtils.decodeBitmap(result.getJpeg(), Integer.MAX_VALUE, Integer.MAX_VALUE); + Bitmap bitmap = CameraUtils.decodeBitmap(result.getData(), Integer.MAX_VALUE, Integer.MAX_VALUE); assertEquals(result.getSize(), size); assertEquals(bitmap.getWidth(), size.getWidth()); assertEquals(bitmap.getHeight(), size.getHeight()); - assertNotNull(result.getJpeg()); + assertNotNull(result.getData()); assertNull(result.getLocation()); assertFalse(result.isSnapshot()); } @@ -526,11 +525,11 @@ public class IntegrationTest extends BaseTest { camera.takePictureSnapshot(); PictureResult result = waitForPicture(true); - Bitmap bitmap = CameraUtils.decodeBitmap(result.getJpeg(), Integer.MAX_VALUE, Integer.MAX_VALUE); + Bitmap bitmap = CameraUtils.decodeBitmap(result.getData(), Integer.MAX_VALUE, Integer.MAX_VALUE); assertEquals(result.getSize(), size); assertEquals(bitmap.getWidth(), size.getWidth()); assertEquals(bitmap.getHeight(), size.getHeight()); - assertNotNull(result.getJpeg()); + assertNotNull(result.getData()); assertNull(result.getLocation()); assertTrue(result.isSnapshot()); } diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/PictureResultTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/PictureResultTest.java index 086fcf88..f3565faf 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/PictureResultTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/PictureResultTest.java @@ -9,8 +9,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; -import java.io.File; - import static org.junit.Assert.assertEquals; @@ -22,21 +20,24 @@ public class PictureResultTest extends BaseTest { @Test public void testResult() { + int format = PictureResult.FORMAT_JPEG; int rotation = 90; Size size = new Size(20, 120); byte[] jpeg = new byte[]{2, 4, 1, 5, 2}; Location location = Mockito.mock(Location.class); boolean isSnapshot = true; + result. format = format; result.rotation = rotation; result.size = size; - result.jpeg = jpeg; + result.data = jpeg; result.location = location; result.isSnapshot = isSnapshot; + assertEquals(result.getFormat(), format); assertEquals(result.getRotation(), rotation); assertEquals(result.getSize(), size); - assertEquals(result.getJpeg(), jpeg); + assertEquals(result.getData(), jpeg); assertEquals(result.getLocation(), location); assertEquals(result.isSnapshot(), isSnapshot); } diff --git a/cameraview/src/main/gles/com/otaliastudios/cameraview/EglBaseSurface.java b/cameraview/src/main/gles/com/otaliastudios/cameraview/EglBaseSurface.java index f0c953a3..cd5858ca 100644 --- a/cameraview/src/main/gles/com/otaliastudios/cameraview/EglBaseSurface.java +++ b/cameraview/src/main/gles/com/otaliastudios/cameraview/EglBaseSurface.java @@ -199,11 +199,11 @@ class EglBaseSurface extends EglElement { } /** - * Saves the EGL surface to jpeg. + * Saves the EGL surface to given format. *
* Expects that this object's EGL surface is current.
*/
- public byte[] saveFrameToJpeg() {
+ public byte[] saveFrameTo(Bitmap.CompressFormat compressFormat) {
if (!mEglCore.isCurrent(mEGLSurface)) {
throw new RuntimeException("Expected EGL context/surface is not current");
}
@@ -231,7 +231,7 @@ class EglBaseSurface extends EglElement {
ByteArrayOutputStream bos = new ByteArrayOutputStream(buf.array().length);
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bmp.copyPixelsFromBuffer(buf);
- bmp.compress(Bitmap.CompressFormat.JPEG, 90, bos);
+ bmp.compress(compressFormat, 90, bos);
bmp.recycle();
return bos.toByteArray();
}
diff --git a/cameraview/src/main/gles/com/otaliastudios/cameraview/EglViewport.java b/cameraview/src/main/gles/com/otaliastudios/cameraview/EglViewport.java
index ca972e09..bda19470 100644
--- a/cameraview/src/main/gles/com/otaliastudios/cameraview/EglViewport.java
+++ b/cameraview/src/main/gles/com/otaliastudios/cameraview/EglViewport.java
@@ -3,6 +3,7 @@ package com.otaliastudios.cameraview;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
+import android.opengl.Matrix;
import java.nio.FloatBuffer;
@@ -17,16 +18,41 @@ class EglViewport extends EglElement {
private static final String SIMPLE_VERTEX_SHADER =
"uniform mat4 uMVPMatrix;\n" +
"uniform mat4 uTexMatrix;\n" +
+ "uniform vec2 uTextureScale;\n" +
"attribute vec4 aPosition;\n" +
"attribute vec4 aTextureCoord;\n" +
"varying vec2 vTextureCoord;\n" +
+ "varying vec2 vTextureScale;\n" +
"void main() {\n" +
" gl_Position = uMVPMatrix * aPosition;\n" +
" vTextureCoord = (uTexMatrix * aTextureCoord).xy;\n" +
+ " vTextureScale = uTextureScale.xy;\n" +
+ "}\n";
+
+ // Circle fragment shader for use with external 2D textures
+ // We draw as transparent all the pixels outside the circle of center (0.5, 0.5)
+ // and radius 0.5. Works well! But we have:
+ // - black in the preview. Solved with canvas.clipPath and a custom GlSurfaceView.
+ // - black in picture snapshots, UNLESS we use PNG which is super slow
+ // - black in videos because they have no alpha channel
+ private static final String CIRCLE_FRAGMENT_SHADER =
+ "#extension GL_OES_EGL_image_external : require\n" +
+ "precision mediump float;\n" +
+ "varying vec2 vTextureCoord;\n" +
+ "varying vec2 vTextureScale;\n" +
+ "uniform samplerExternalOES sTexture;\n" +
+ "vec2 axis = vec2(0.5, 0.5);\n" +
+ "vec2 center = vec2(0.5, 0.5);\n" +
+ "vec4 none = vec4(0.0, 0.0, 0.0, 0.0);\n" +
+ "void main() {\n" +
+ " if (pow((vTextureCoord.x - center.x) / (axis.x * vTextureScale.x), 2.0) + pow((vTextureCoord.y - center.y) / (axis.y * vTextureScale.y), 2.0) > 1.0) {\n" +
+ " gl_FragColor = none;\n" +
+ " } else {\n" +
+ " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
+ " }\n" +
"}\n";
// Simple fragment shader for use with external 2D textures
- // (e.g. what we get from SurfaceTexture).
private static final String SIMPLE_FRAGMENT_SHADER =
"#extension GL_OES_EGL_image_external : require\n" +
"precision mediump float;\n" +
@@ -56,12 +82,9 @@ class EglViewport extends EglElement {
};
// Stuff from Drawable2d.FULL_RECTANGLE
+ private final static int VERTEX_COUNT = FULL_RECTANGLE_COORDS.length / 2;
private FloatBuffer mVertexCoordinatesArray = floatBuffer(FULL_RECTANGLE_COORDS);
private FloatBuffer mTextureCoordinatesArray = floatBuffer(FULL_RECTANGLE_TEX_COORDS);
- private int mVertexCount = FULL_RECTANGLE_COORDS.length / 2;
- private final int mCoordinatesPerVertex = 2;
- private final int mVertexStride = 8;
- private final int mTextureStride = 8;
// Stuff from Texture2dProgram
private int mProgramHandle;
@@ -71,6 +94,7 @@ class EglViewport extends EglElement {
private int muTexMatrixLocation;
private int maPositionLocation;
private int maTextureCoordLocation;
+ private int muTextureScaleLocation;
// private int muKernelLoc; // Used for filtering
// private int muTexOffsetLoc; // Used for filtering
@@ -78,11 +102,13 @@ class EglViewport extends EglElement {
EglViewport() {
mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES;
- mProgramHandle = createProgram(SIMPLE_VERTEX_SHADER, SIMPLE_FRAGMENT_SHADER);
+ mProgramHandle = createProgram(SIMPLE_VERTEX_SHADER, CIRCLE_FRAGMENT_SHADER);
maPositionLocation = GLES20.glGetAttribLocation(mProgramHandle, "aPosition");
checkLocation(maPositionLocation, "aPosition");
maTextureCoordLocation = GLES20.glGetAttribLocation(mProgramHandle, "aTextureCoord");
checkLocation(maTextureCoordLocation, "aTextureCoord");
+ muTextureScaleLocation = GLES20.glGetUniformLocation(mProgramHandle, "uTextureScale");
+ checkLocation(muTextureScaleLocation, "uTextureScale");
muMVPMatrixLocation = GLES20.glGetUniformLocation(mProgramHandle, "uMVPMatrix");
checkLocation(muMVPMatrixLocation, "uMVPMatrix");
muTexMatrixLocation = GLES20.glGetUniformLocation(mProgramHandle, "uTexMatrix");
@@ -110,14 +136,10 @@ class EglViewport extends EglElement {
GLES20.glBindTexture(mTextureTarget, texId);
check("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);
+ 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);
check("glTexParameter");
return texId;
@@ -125,16 +147,34 @@ class EglViewport extends EglElement {
void drawFrame(int textureId, float[] textureMatrix) {
drawFrame(textureId, textureMatrix,
- IDENTITY_MATRIX, mVertexCoordinatesArray, 0,
- mVertexCount, mCoordinatesPerVertex,
- mVertexStride, mTextureCoordinatesArray,
- mTextureStride);
+ mVertexCoordinatesArray,
+ mTextureCoordinatesArray, 1F, 1F);
+ }
+
+ void drawFrame(int textureId, float[] textureMatrix, float textureScaleX, float textureScaleY) {
+ drawFrame(textureId, textureMatrix,
+ mVertexCoordinatesArray,
+ mTextureCoordinatesArray, textureScaleX, textureScaleY);
}
+ private static final FloatBuffer TEMP = floatBuffer(new float[] { 1.0F, 1.0F });
+ /**
+ * The issue with the CIRCLE shader is that if the textureMatrix has a scale value,
+ * it fails miserably, not taking the scale into account.
+ * So what we can do here is
+ *
+ * - read textureMatrix scaleX and scaleY values. This is pretty much impossible to do from the matrix itself
+ * without making risky assumptions over the order of operations.
+ * https://www.opengl.org/discussion_boards/showthread.php/159215-Is-it-possible-to-extract-rotation-translation-scale-given-a-matrix
+ * So we prefer passing scaleX and scaleY here to the draw function.
+ * - pass these values to the vertex shader
+ * - pass them to the fragment shader
+ * - in the fragment shader, take this scale value into account
+ */
private void drawFrame(int textureId, float[] textureMatrix,
- float[] mvpMatrix, FloatBuffer vertexBuffer, int firstVertex,
- int vertexCount, int coordsPerVertex, int vertexStride,
- FloatBuffer texBuffer, int texStride) {
+ FloatBuffer vertexBuffer,
+ FloatBuffer texBuffer,
+ float textureScaleX, float textureScaleY) {
check("draw start");
// Select the program.
@@ -146,33 +186,34 @@ class EglViewport extends EglElement {
GLES20.glBindTexture(mTextureTarget, textureId);
// Copy the model / view / projection matrix over.
- GLES20.glUniformMatrix4fv(muMVPMatrixLocation, 1, false, mvpMatrix, 0);
+ GLES20.glUniformMatrix4fv(muMVPMatrixLocation, 1, false, IDENTITY_MATRIX, 0);
check("glUniformMatrix4fv");
// Copy the texture transformation matrix over.
GLES20.glUniformMatrix4fv(muTexMatrixLocation, 1, false, textureMatrix, 0);
check("glUniformMatrix4fv");
+ // Pass scale values.
+ GLES20.glUniform2f(muTextureScaleLocation, textureScaleX, textureScaleY);
+ check("glUniform2f");
+
// Enable the "aPosition" vertex attribute.
+ // Connect vertexBuffer to "aPosition".
GLES20.glEnableVertexAttribArray(maPositionLocation);
check("glEnableVertexAttribArray");
-
- // Connect vertexBuffer to "aPosition".
- GLES20.glVertexAttribPointer(maPositionLocation, coordsPerVertex,
- GLES20.GL_FLOAT, false, vertexStride, vertexBuffer);
+ GLES20.glVertexAttribPointer(maPositionLocation, 2, GLES20.GL_FLOAT, false, 8, vertexBuffer);
check("glVertexAttribPointer");
// Enable the "aTextureCoord" vertex attribute.
+ // Connect texBuffer to "aTextureCoord".
GLES20.glEnableVertexAttribArray(maTextureCoordLocation);
check("glEnableVertexAttribArray");
-
- // Connect texBuffer to "aTextureCoord".
- GLES20.glVertexAttribPointer(maTextureCoordLocation, 2,
- GLES20.GL_FLOAT, false, texStride, texBuffer);
+ GLES20.glVertexAttribPointer(maTextureCoordLocation, 2, GLES20.GL_FLOAT, false, 8, texBuffer);
check("glVertexAttribPointer");
+
// Draw the rect.
- GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, firstVertex, vertexCount);
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, VERTEX_COUNT);
check("glDrawArrays");
// Done -- disable vertex array, texture, and program.
diff --git a/cameraview/src/main/gles/com/otaliastudios/cameraview/TextureMediaEncoder.java b/cameraview/src/main/gles/com/otaliastudios/cameraview/TextureMediaEncoder.java
index 0229194f..394ff7a8 100644
--- a/cameraview/src/main/gles/com/otaliastudios/cameraview/TextureMediaEncoder.java
+++ b/cameraview/src/main/gles/com/otaliastudios/cameraview/TextureMediaEncoder.java
@@ -123,7 +123,7 @@ class TextureMediaEncoder extends VideoMediaEncoder