Fix snapshot cropOutput, crop only when needed

pull/1/head
Mattia Iavarone 7 years ago
parent fa9fba1f90
commit 2c0bc641eb
  1. 16
      camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java
  2. 20
      camerakit/src/main/base/com/flurgle/camerakit/PreviewImpl.java
  3. 27
      camerakit/src/main/java/com/flurgle/camerakit/CameraView.java
  4. 6
      camerakit/src/main/utils/com/flurgle/camerakit/CropHelper.java

@ -388,7 +388,7 @@ class Camera1 extends CameraImpl {
public void onPictureTaken(byte[] data, Camera camera) { public void onPictureTaken(byte[] data, Camera camera) {
mIsCapturingImage = false; mIsCapturingImage = false;
camera.startPreview(); // This is needed, read somewhere in the docs. camera.startPreview(); // This is needed, read somewhere in the docs.
mCameraListener.processJpegPicture(data, consistentWithView, exifFlip); mCameraListener.processImage(data, consistentWithView, exifFlip);
} }
}); });
} }
@ -405,10 +405,12 @@ class Camera1 extends CameraImpl {
public void onPreviewFrame(final byte[] data, Camera camera) { public void onPreviewFrame(final byte[] data, Camera camera) {
// Got to rotate the preview frame, since byte[] data here does not include // 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. // EXIF tags automatically set by camera. So either we add EXIF, or we rotate.
// TODO: add exif, and also care about flipping. // Adding EXIF to a byte array, unfortunately, is hard.
Camera.Parameters params = mCamera.getParameters(); Camera.Parameters params = mCamera.getParameters();
final int rotation = computeExifRotation(); final int sensorToDevice = computeExifRotation();
final boolean flip = rotation % 180 != 0; final int sensorToDisplay = computeSensorToDisplayOffset();
final boolean exifFlip = computeExifFlip();
final boolean flip = sensorToDevice % 180 != 0;
final int preWidth = mPreviewSize.getWidth(); final int preWidth = mPreviewSize.getWidth();
final int preHeight = mPreviewSize.getHeight(); final int preHeight = mPreviewSize.getHeight();
final int postWidth = flip ? preHeight : preWidth; final int postWidth = flip ? preHeight : preWidth;
@ -417,9 +419,11 @@ class Camera1 extends CameraImpl {
new Thread(new Runnable() { new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
byte[] rotatedData = RotationHelper.rotate(data, preWidth, preHeight, rotation);
final boolean consistentWithView = (sensorToDevice + sensorToDisplay + 180) % 180 == 0;
byte[] rotatedData = RotationHelper.rotate(data, preWidth, preHeight, sensorToDevice);
YuvImage yuv = new YuvImage(rotatedData, format, postWidth, postHeight, null); YuvImage yuv = new YuvImage(rotatedData, format, postWidth, postHeight, null);
mCameraListener.processYuvPicture(yuv); mCameraListener.processSnapshot(yuv, consistentWithView, exifFlip);
mIsCapturingImage = false; mIsCapturingImage = false;
} }
}).start(); }).start();

@ -49,7 +49,7 @@ abstract class PreviewImpl {
void setDesiredSize(int width, int height) { void setDesiredSize(int width, int height) {
this.mDesiredWidth = width; this.mDesiredWidth = width;
this.mDesiredHeight = height; this.mDesiredHeight = height;
refreshScale(); crop();
} }
final Size getSurfaceSize() { final Size getSurfaceSize() {
@ -64,7 +64,7 @@ abstract class PreviewImpl {
protected final void onSurfaceAvailable(int width, int height) { protected final void onSurfaceAvailable(int width, int height) {
mSurfaceWidth = width; mSurfaceWidth = width;
mSurfaceHeight = height; mSurfaceHeight = height;
refreshScale(); crop();
mSurfaceCallback.onSurfaceAvailable(); mSurfaceCallback.onSurfaceAvailable();
} }
@ -75,7 +75,7 @@ abstract class PreviewImpl {
if (width != mSurfaceWidth || height != mSurfaceHeight) { if (width != mSurfaceWidth || height != mSurfaceHeight) {
mSurfaceWidth = width; mSurfaceWidth = width;
mSurfaceHeight = height; mSurfaceHeight = height;
refreshScale(); crop();
mSurfaceCallback.onSurfaceChanged(); mSurfaceCallback.onSurfaceChanged();
} }
} }
@ -84,7 +84,7 @@ abstract class PreviewImpl {
protected final void onSurfaceDestroyed() { protected final void onSurfaceDestroyed() {
mSurfaceWidth = 0; mSurfaceWidth = 0;
mSurfaceHeight = 0; mSurfaceHeight = 0;
refreshScale(); crop();
} }
@ -93,7 +93,7 @@ abstract class PreviewImpl {
* to match the desired aspect ratio. * to match the desired aspect ratio.
* This means that the external part of the surface will be cropped by the outer view. * This means that the external part of the surface will be cropped by the outer view.
*/ */
private final void refreshScale() { private final void crop() {
getView().post(new Runnable() { getView().post(new Runnable() {
@Override @Override
public void run() { public void run() {
@ -116,4 +116,14 @@ abstract class PreviewImpl {
} }
}); });
} }
/**
* Whether we are cropping the output.
* If false, this means that the output image will match the visible bounds.
* @return true if cropping
*/
final boolean isCropping() {
return getView().getScaleX() > 1 || getView().getScaleY() > 1;
}
} }

@ -983,23 +983,20 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
* @param consistentWithView is the final image (decoded respecting EXIF data) consistent with * @param consistentWithView is the final image (decoded respecting EXIF data) consistent with
* the view width and height? Or should we flip dimensions to have a * the view width and height? Or should we flip dimensions to have a
* consistent measure? * consistent measure?
* @param flipPicture whether this picture should be flipped horizontally after decoding, * @param flipHorizontally whether this picture should be flipped horizontally after decoding,
* because it was taken with the front camera. * because it was taken with the front camera.
*/ */
public void processJpegPicture(final byte[] jpeg, final boolean consistentWithView, final boolean flipPicture) { public void processImage(final byte[] jpeg, final boolean consistentWithView, final boolean flipHorizontally) {
getWorkerHandler().post(new Runnable() { getWorkerHandler().post(new Runnable() {
@Override @Override
public void run() { public void run() {
byte[] jpeg2 = jpeg; byte[] jpeg2 = jpeg;
if (mCropOutput) { if (mCropOutput && mPreviewImpl.isCropping()) {
// If consistent, dimensions of the jpeg Bitmap and dimensions of getWidth(), getHeight() // If consistent, dimensions of the jpeg Bitmap and dimensions of getWidth(), getHeight()
// Live in the same reference system. // Live in the same reference system.
AspectRatio targetRatio; int w = consistentWithView ? getWidth() : getHeight();
if (consistentWithView) { int h = consistentWithView ? getHeight() : getWidth();
targetRatio = AspectRatio.of(getWidth(), getHeight()); AspectRatio targetRatio = AspectRatio.of(w, h);
} else {
targetRatio = AspectRatio.of(getHeight(), getWidth());
}
Log.e(TAG, "is Consistent? " + consistentWithView); Log.e(TAG, "is Consistent? " + consistentWithView);
Log.e(TAG, "viewWidth? " + getWidth() + ", viewHeight? " + getHeight()); Log.e(TAG, "viewWidth? " + getWidth() + ", viewHeight? " + getHeight());
jpeg2 = CropHelper.cropToJpeg(jpeg, targetRatio, mJpegQuality); jpeg2 = CropHelper.cropToJpeg(jpeg, targetRatio, mJpegQuality);
@ -1010,11 +1007,13 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
} }
public void processYuvPicture(YuvImage yuv) { public void processSnapshot(YuvImage yuv, boolean consistentWithView, boolean flipHorizontally) {
byte[] jpeg; byte[] jpeg;
if (mCropOutput) { if (mCropOutput && mPreviewImpl.isCropping()) {
AspectRatio outputRatio = AspectRatio.of(getWidth(), getHeight()); int w = consistentWithView ? getWidth() : getHeight();
jpeg = CropHelper.cropToJpeg(yuv, outputRatio, mJpegQuality); int h = consistentWithView ? getHeight() : getWidth();
AspectRatio targetRatio = AspectRatio.of(w, h);
jpeg = CropHelper.cropToJpeg(yuv, targetRatio, mJpegQuality);
} else { } else {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
yuv.compressToJpeg(new Rect(0, 0, yuv.getWidth(), yuv.getHeight()), mJpegQuality, out); yuv.compressToJpeg(new Rect(0, 0, yuv.getWidth(), yuv.getHeight()), mJpegQuality, out);

@ -24,8 +24,8 @@ public class CropHelper {
// This reads a rotated Bitmap thanks to CameraUtils. Then crops and returns a byte array. // This reads a rotated Bitmap thanks to CameraUtils. Then crops and returns a byte array.
// In doing so, EXIF data is deleted. // In doing so, EXIF data is deleted.
public static byte[] cropToJpeg(byte[] jpeg, AspectRatio targetRatio, int jpegCompression) { public static byte[] cropToJpeg(byte[] jpeg, AspectRatio targetRatio, int jpegCompression) {
Bitmap image = CameraUtils.decodeBitmap(jpeg); Bitmap image = CameraUtils.decodeBitmap(jpeg);
Log.e("CropHelper", "decoded image has width="+image.getWidth()+", height="+image.getHeight());
Rect cropRect = computeCrop(image.getWidth(), image.getHeight(), targetRatio); Rect cropRect = computeCrop(image.getWidth(), image.getHeight(), targetRatio);
Bitmap crop = Bitmap.createBitmap(image, cropRect.left, cropRect.top, cropRect.width(), cropRect.height()); Bitmap crop = Bitmap.createBitmap(image, cropRect.left, cropRect.top, cropRect.width(), cropRect.height());
image.recycle(); image.recycle();
@ -44,10 +44,10 @@ public class CropHelper {
x = (currentWidth - width) / 2; x = (currentWidth - width) / 2;
} else { } else {
width = currentWidth; width = currentWidth;
height = (int) (width * targetRatio.inverse().toFloat()); height = (int) (width / targetRatio.toFloat());
y = (currentHeight - height) / 2; y = (currentHeight - height) / 2;
x = 0; x = 0;
} }
return new Rect(x, y, x+width, y+height); return new Rect(x, y, x + width, y + height);
} }
} }

Loading…
Cancel
Save