getPictureSize and getPreviewSize now return sizes in the output reference

v2
Mattia Iavarone 7 years ago
parent fca9d3fbd8
commit 0c51d63e9c
  1. 18
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CropHelperTest.java
  2. 2
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraController.java
  3. 43
      cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java
  4. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/Camera2.java
  5. 77
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java
  6. 29
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  7. 31
      cameraview/src/main/utils/com/otaliastudios/cameraview/CropHelper.java
  8. 26
      cameraview/src/main/utils/com/otaliastudios/cameraview/RotationHelper.java
  9. 15
      demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java

@ -1,37 +1,23 @@
package com.otaliastudios.cameraview;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class YuvHelperTest extends BaseTest {
public class CropHelperTest extends BaseTest {
@Test
public void testCrop() {
@ -42,7 +28,7 @@ public class YuvHelperTest extends BaseTest {
private void testCrop(final Size inSize, final AspectRatio outRatio) {
AspectRatio inRatio = AspectRatio.of(inSize.getWidth(), inSize.getHeight());
Rect out = YuvHelper.computeCrop(inSize, outRatio);
Rect out = CropHelper.computeCrop(inSize, outRatio);
Size outSize = new Size(out.width(), out.height());
assertTrue(outRatio.matches(outSize));

@ -97,7 +97,7 @@ public class MockCameraController extends CameraController {
}
@Override
void takePictureSnapshot(boolean shouldCrop, AspectRatio viewAspectRatio) {
void takePictureSnapshot(AspectRatio viewAspectRatio) {
}
@Override

@ -138,11 +138,8 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
LOG.i(log, "Dispatching onCameraPreviewSizeChanged.");
mCameraCallbacks.onCameraPreviewSizeChanged();
boolean invertPreviewSizes = shouldFlipSizes();
mPreview.setDesiredSize(
invertPreviewSizes ? mPreviewSize.getHeight() : mPreviewSize.getWidth(),
invertPreviewSizes ? mPreviewSize.getWidth() : mPreviewSize.getHeight()
);
Size previewSize = getPreviewSize(REF_VIEW);
mPreview.setDesiredSize(previewSize.getWidth(), previewSize.getHeight());
Camera.Parameters params = mCamera.getParameters();
mPreviewFormat = params.getPreviewFormat();
@ -183,7 +180,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
// Set parameters that might have been set before the camera was opened.
LOG.i("onStart:", "Applying default parameters.");
Camera.Parameters params = mCamera.getParameters();
mCameraOptions = new CameraOptions(params, shouldFlipSizes());
mCameraOptions = new CameraOptions(params, flip(REF_SENSOR, REF_VIEW));
applyDefaultFocus(params);
mergeFlash(params, Flash.DEFAULT);
mergeLocation(params, null);
@ -194,7 +191,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
mCamera.setParameters(params);
// Try starting preview.
mCamera.setDisplayOrientation(computeSensorToViewOffset()); // <- not allowed during preview
mCamera.setDisplayOrientation(offset(REF_SENSOR, REF_VIEW)); // <- not allowed during preview
if (shouldBindToSurface()) bindToSurface();
LOG.i("onStart:", "Ended");
}
@ -511,13 +508,8 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
if (mIsCapturingVideo && !mCameraOptions.isVideoSnapshotSupported()) return;
mIsCapturingImage = true;
final int sensorToOutput = computeSensorToOutputOffset();
int outputWidth = mPictureSize.getWidth();
int outputHeight = mPictureSize.getHeight();
//noinspection SuspiciousNameCombination
final Size outputSize = sensorToOutput % 180 == 0 ?
new Size(outputWidth, outputHeight) :
new Size(outputHeight, outputWidth);
final int sensorToOutput = offset(REF_SENSOR, REF_OUTPUT);
final Size outputSize = getPictureSize(REF_OUTPUT);
Camera.Parameters params = mCamera.getParameters();
params.setRotation(sensorToOutput);
mCamera.setParameters(params);
@ -559,7 +551,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
@Override
void takePictureSnapshot(final boolean shouldCrop, final AspectRatio viewAspectRatio) {
void takePictureSnapshot(final AspectRatio viewAspectRatio) {
LOG.v("takePictureSnapshot: scheduling");
schedule(null, true, new Runnable() {
@Override
@ -582,12 +574,9 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
// Got to rotate the preview frame, since byte[] data here does not include
// EXIF tags automatically set by camera. So either we add EXIF, or we rotate.
// Adding EXIF to a byte array, unfortunately, is hard.
final int sensorToOutput = computeSensorToOutputOffset();
final int sensorToView = computeSensorToViewOffset();
final boolean outputMatchesView = (sensorToOutput + sensorToView + 180) % 180 == 0;
final Size originalSize = new Size(mPreviewSize.getWidth(), mPreviewSize.getHeight());
final Size outputSize = sensorToOutput % 180 == 0 ? originalSize : originalSize.flip();
final AspectRatio outputRatio = outputMatchesView ? viewAspectRatio : viewAspectRatio.inverse();
final int sensorToOutput = offset(REF_SENSOR, REF_OUTPUT);
final AspectRatio outputRatio = flip(REF_OUTPUT, REF_VIEW) ? viewAspectRatio.inverse() : viewAspectRatio;
final Size outputSize = getPreviewSize(REF_OUTPUT);
final int format = mPreviewFormat;
WorkerHandler.run(new Runnable() {
@Override
@ -595,12 +584,12 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
// Rotate the picture, because no one will write EXIF data,
// then crop if needed. In both cases, transform yuv to jpeg.
LOG.v("takePictureSnapshot:", "rotating.");
byte[] data = YuvHelper.rotate(yuv, originalSize, sensorToOutput);
byte[] data = RotationHelper.rotate(yuv, mPreviewSize, sensorToOutput);
YuvImage yuv = new YuvImage(data, format, outputSize.getWidth(), outputSize.getHeight(), null);
LOG.v("takePictureSnapshot:", "rotated. Cropping and transforming to jpeg.");
ByteArrayOutputStream stream = new ByteArrayOutputStream();
Rect outputRect = YuvHelper.computeCrop(outputSize, outputRatio);
Rect outputRect = CropHelper.computeCrop(outputSize, outputRatio);
yuv.compressToJpeg(outputRect, 90, stream);
data = stream.toByteArray();
@ -630,7 +619,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
public void onPreviewFrame(byte[] data, Camera camera) {
Frame frame = mFrameManager.getFrame(data,
System.currentTimeMillis(),
computeSensorToOutputOffset(),
offset(REF_SENSOR, REF_OUTPUT),
mPreviewSize,
mPreviewFormat);
mCameraCallbacks.dispatchFrame(frame);
@ -677,8 +666,8 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
mVideoResult.isSnapshot = false;
mVideoResult.codec = mVideoCodec;
mVideoResult.location = mLocation;
mVideoResult.rotation = computeSensorToOutputOffset();
mVideoResult.size = mVideoResult.rotation % 180 == 0 ? videoSize : videoSize.flip();
mVideoResult.rotation = offset(REF_SENSOR, REF_OUTPUT);
mVideoResult.size = flip(REF_SENSOR, REF_OUTPUT) ? videoSize.flip() : videoSize;
// Initialize the media recorder
mCamera.unlock();
@ -871,7 +860,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
if (!mCameraOptions.isAutoFocusSupported()) return;
final PointF p = new PointF(point.x, point.y); // copy.
List<Camera.Area> meteringAreas2 = computeMeteringAreas(p.x, p.y,
viewWidthF, viewHeightF, computeSensorToViewOffset());
viewWidthF, viewHeightF, offset(REF_SENSOR, REF_VIEW));
List<Camera.Area> meteringAreas1 = meteringAreas2.subList(0, 1);
// At this point we are sure that camera supports auto focus... right? Look at CameraView.onTouchEvent().

@ -91,7 +91,7 @@ class Camera2 extends CameraController {
}
@Override
void takePictureSnapshot(boolean shouldCrop, AspectRatio viewAspectRatio) {
void takePictureSnapshot(AspectRatio viewAspectRatio) {
}

@ -30,6 +30,10 @@ abstract class CameraController implements
static final int STATE_STARTING = 1; // Camera is about to start.
static final int STATE_STARTED = 2; // Camera is available and we can set parameters.
static final int REF_SENSOR = 0;
static final int REF_VIEW = 1;
static final int REF_OUTPUT = 2;
protected final CameraView.CameraCallbacks mCameraCallbacks;
protected CameraPreview mPreview;
protected WorkerHandler mHandler;
@ -326,7 +330,7 @@ abstract class CameraController implements
abstract void takePicture();
abstract void takePictureSnapshot(boolean shouldCrop, AspectRatio viewAspectRatio);
abstract void takePictureSnapshot(AspectRatio viewAspectRatio);
abstract void takeVideo(@NonNull File file);
@ -393,10 +397,6 @@ abstract class CameraController implements
return mPictureSizeSelector;
}
final Size getPictureSize() {
return mPictureSize;
}
final float getZoomValue() {
return mZoomValue;
}
@ -405,10 +405,6 @@ abstract class CameraController implements
return mExposureCorrectionValue;
}
final Size getPreviewSize() {
return mPreviewSize;
}
final boolean isTakingVideo() {
return mIsCapturingVideo;
}
@ -417,37 +413,15 @@ abstract class CameraController implements
//region Orientation utils
/**
* The result of this should not change after start() is called: the sensor offset is the same,
* and the display offset does not change.
*/
final boolean shouldFlipSizes() {
int offset = computeSensorToViewOffset();
LOG.i("shouldFlipSizes:", "displayOffset=", mDisplayOffset, "sensorOffset=", mSensorOffset);
LOG.i("shouldFlipSizes:", "sensorToDisplay=", offset);
return offset % 180 != 0;
}
/**
* Returns how much should the sensor image be rotated before being shown.
* It is meant to be fed to Camera.setDisplayOrientation().
* The result of this should not change after start() is called: the sensor offset is the same,
* and the display offset does not change.
*/
protected final int computeSensorToViewOffset() {
private int computeSensorToViewOffset() {
if (mFacing == Facing.FRONT) {
// Here we had ((mSensorOffset - mDisplayOffset) + 360 + 180) % 360
// And it seemed to give the same results for various combinations, but not for all (e.g. 0 - 270).
return (360 - ((mSensorOffset + mDisplayOffset) % 360)) % 360;
} else {
return (mSensorOffset - mDisplayOffset + 360) % 360;
}
}
/**
* Returns the orientation to be set as a exif tag.
*/
protected final int computeSensorToOutputOffset() {
private int computeSensorToOutputOffset() {
if (mFacing == Facing.FRONT) {
return (mSensorOffset - mDeviceOrientation + 360) % 360;
} else {
@ -455,6 +429,39 @@ abstract class CameraController implements
}
}
// Returns the offset between two reference systems.
final int offset(int fromReference, int toReference) {
if (fromReference == toReference) return 0;
// We only know how to compute offsets with respect to REF_SENSOR.
// That's why we separate the two cases.
if (fromReference == REF_SENSOR) {
return toReference == REF_VIEW ?
computeSensorToViewOffset() :
computeSensorToOutputOffset();
}
// Maybe the sensor is the other.
if (toReference == REF_SENSOR) {
return -offset(toReference, fromReference) + 360;
}
// None of them is the sensor. Use a difference.
return (offset(REF_SENSOR, toReference) - offset(REF_SENSOR, fromReference) + 360) % 360;
}
final boolean flip(int reference1, int reference2) {
return offset(reference1, reference2) % 180 != 0;
}
final Size getPictureSize(int reference) {
if (mPictureSize == null) return null;
return flip(REF_SENSOR, reference) ? mPictureSize.flip() : mPictureSize;
}
final Size getPreviewSize(int reference) {
if (mPreviewSize == null) return null;
return flip(REF_SENSOR, reference) ? mPreviewSize.flip() : mPreviewSize;
}
//endregion
//region Size utils
@ -469,7 +476,7 @@ abstract class CameraController implements
protected final Size computePictureSize() {
// The external selector is expecting stuff in the view world, not in the sensor world.
// Use the list in the camera options, then flip the result if needed.
boolean flip = shouldFlipSizes();
boolean flip = flip(REF_SENSOR, REF_VIEW);
SizeSelector selector;
if (mSessionType == SessionType.PICTURE) {
@ -500,7 +507,7 @@ abstract class CameraController implements
protected final Size computePreviewSize(List<Size> previewSizes) {
// instead of flipping everything to the view world, we can just flip the
// surface size to the sensor world
boolean flip = shouldFlipSizes();
boolean flip = flip(REF_SENSOR, REF_VIEW);
AspectRatio targetRatio = AspectRatio.of(mPictureSize.getWidth(), mPictureSize.getHeight());
Size targetMinSize = mPreview.getSurfaceSize();
if (flip) targetMinSize = targetMinSize.flip();

@ -15,6 +15,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.TypedArray;
import android.graphics.PointF;
import android.graphics.Rect;
import android.location.Location;
import android.media.MediaActionSound;
import android.os.Build;
@ -260,7 +261,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Size previewSize = getPreviewSize();
Size previewSize = mCameraController.getPreviewSize(CameraController.REF_VIEW);
if (previewSize == null) {
LOG.w("onMeasure:", "surface is not ready. Calling default behavior.");
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
@ -272,9 +273,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int widthValue = MeasureSpec.getSize(widthMeasureSpec);
final int heightValue = MeasureSpec.getSize(heightMeasureSpec);
final boolean flip = mCameraController.shouldFlipSizes();
final float previewWidth = flip ? previewSize.getHeight() : previewSize.getWidth();
final float previewHeight = flip ? previewSize.getWidth() : previewSize.getHeight();
final float previewWidth = previewSize.getWidth();
final float previewHeight = previewSize.getHeight();
// Pre-process specs
final ViewGroup.LayoutParams lp = getLayoutParams();
@ -1144,9 +1144,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
* @see #takePicture()
*/
public void takePictureSnapshot() {
mCameraController.takePictureSnapshot(
mCameraPreview.isCropping(),
AspectRatio.of(getWidth(), getHeight()));
if (getWidth() == 0 || getHeight() == 0) return;
mCameraController.takePictureSnapshot(AspectRatio.of(getWidth(), getHeight()));
}
@ -1222,7 +1221,19 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
*/
@Nullable
public Size getPreviewSize() {
return mCameraController != null ? mCameraController.getPreviewSize() : null;
if (getWidth() == 0 || getHeight() == 0) return null;
// Get the preview size and crop according to the current view size.
// It's better to do calculations in the REF_VIEW reference, and then flip if needed.
Size preview = mCameraController.getPreviewSize(CameraController.REF_VIEW);
AspectRatio viewRatio = AspectRatio.of(getWidth(), getHeight());
Rect crop = CropHelper.computeCrop(preview, viewRatio);
Size cropSize = new Size(crop.width(), crop.height());
if (mCameraController.flip(CameraController.REF_VIEW, CameraController.REF_OUTPUT)) {
return cropSize.flip();
} else {
return cropSize;
}
}
@ -1234,7 +1245,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
*/
@Nullable
public Size getPictureSize() {
return mCameraController != null ? mCameraController.getPictureSize() : null;
return mCameraController.getPictureSize(CameraController.REF_OUTPUT);
}

@ -0,0 +1,31 @@
package com.otaliastudios.cameraview;
import android.graphics.Rect;
class CropHelper {
static Rect computeCrop(Size currentSize, AspectRatio targetRatio) {
int currentWidth = currentSize.getWidth();
int currentHeight = currentSize.getHeight();
if (targetRatio.matches(currentSize)) {
return new Rect(0, 0, currentWidth, currentHeight);
}
// They are not equal. Compute.
AspectRatio currentRatio = AspectRatio.of(currentWidth, currentHeight);
int x, y, width, height;
if (currentRatio.toFloat() > targetRatio.toFloat()) {
height = currentHeight;
width = (int) (height * targetRatio.toFloat());
y = 0;
x = (currentWidth - width) / 2;
} else {
width = currentWidth;
height = (int) (width / targetRatio.toFloat());
y = (currentHeight - height) / 2;
x = 0;
}
return new Rect(x, y, x + width, y + height);
}
}

@ -2,31 +2,7 @@ package com.otaliastudios.cameraview;
import android.graphics.Rect;
class YuvHelper {
static Rect computeCrop(Size currentSize, AspectRatio targetRatio) {
int currentWidth = currentSize.getWidth();
int currentHeight = currentSize.getHeight();
if (targetRatio.matches(currentSize)) {
return new Rect(0, 0, currentWidth, currentHeight);
}
// They are not equal. Compute.
AspectRatio currentRatio = AspectRatio.of(currentWidth, currentHeight);
int x, y, width, height;
if (currentRatio.toFloat() > targetRatio.toFloat()) {
height = currentHeight;
width = (int) (height * targetRatio.toFloat());
y = 0;
x = (currentWidth - width) / 2;
} else {
width = currentWidth;
height = (int) (width / targetRatio.toFloat());
y = (currentHeight - height) / 2;
x = 0;
}
return new Rect(x, y, x + width, y + height);
}
class RotationHelper {
static byte[] rotate(final byte[] yuv, final Size size, final int rotation) {
if (rotation == 0) return yuv;

@ -34,7 +34,6 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
private boolean mCapturingVideo;
// To show stuff in the callback
private Size mCaptureNativeSize;
private long mCaptureTime;
@Override
@ -94,27 +93,24 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
}
}
private void onPicture(byte[] jpeg) {
private void onPicture(PictureResult result) {
mCapturingPicture = false;
long callbackTime = System.currentTimeMillis();
if (mCapturingVideo) {
message("Captured while taking video. Size="+mCaptureNativeSize, false);
message("Captured while taking video. Size=" + result.getSize(), false);
return;
}
// This can happen if picture was taken with a gesture.
if (mCaptureTime == 0) mCaptureTime = callbackTime - 300;
if (mCaptureNativeSize == null) mCaptureNativeSize = camera.getPictureSize();
PicturePreviewActivity.setImage(jpeg);
PicturePreviewActivity.setImage(result.getJpeg());
Intent intent = new Intent(CameraActivity.this, PicturePreviewActivity.class);
intent.putExtra("delay", callbackTime - mCaptureTime);
intent.putExtra("nativeWidth", mCaptureNativeSize.getWidth());
intent.putExtra("nativeHeight", mCaptureNativeSize.getHeight());
intent.putExtra("nativeWidth", result.getSize().getWidth());
intent.putExtra("nativeHeight", result.getSize().getHeight());
startActivity(intent);
mCaptureTime = 0;
mCaptureNativeSize = null;
}
private void onVideo(File video) {
@ -153,7 +149,6 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
if (mCapturingPicture) return;
mCapturingPicture = true;
mCaptureTime = System.currentTimeMillis();
mCaptureNativeSize = camera.getPictureSize();
message("Capturing picture...", false);
camera.takePicture();
}

Loading…
Cancel
Save