Repackage / Expose (#482)
	
		
	
				
					
				
			* Refactor code into subpackages * Rename CameraController to CameraEngine * Move Engine and Previews * Repackage everything else * Refactor and document some packages * Refactor cameraview package * Refactor Size package * Refactor unit tests * Refactor preview package * Refactor picture package * Refactor video package * Refactor encoding package * Refactor androidTests * Fix tests * Fix GestureLayout tests * Document changespull/484/head
							parent
							
								
									0c7726d5c5
								
							
						
					
					
						commit
						cd5f0a12bf
					
				| @ -1,137 +0,0 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| 
 | ||||
| import android.graphics.PointF; | ||||
| import android.location.Location; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| 
 | ||||
| import java.io.File; | ||||
| 
 | ||||
| public class MockCameraController extends CameraController { | ||||
| 
 | ||||
|     boolean mPictureCaptured; | ||||
|     boolean mFocusStarted; | ||||
|     boolean mZoomChanged; | ||||
|     boolean mExposureCorrectionChanged; | ||||
| 
 | ||||
|     MockCameraController(CameraView.CameraCallbacks callback) { | ||||
|         super(callback); | ||||
|     } | ||||
| 
 | ||||
|     void setMockCameraOptions(CameraOptions options) { | ||||
|         mCameraOptions = options; | ||||
|     } | ||||
| 
 | ||||
|     void setMockPreviewStreamSize(Size size) { | ||||
|         mPreviewStreamSize = size; | ||||
|     } | ||||
| 
 | ||||
|     void mockStarted(boolean started) { | ||||
|         mState = started ? STATE_STARTED : STATE_STOPPED; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void onStart() { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void onStop() { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setZoom(float zoom, @Nullable PointF[] points, boolean notify) { | ||||
|         mZoomValue = zoom; | ||||
|         mZoomChanged = true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setExposureCorrection(float EVvalue, @NonNull float[] bounds, @Nullable PointF[] points, boolean notify) { | ||||
|         mExposureCorrectionValue = EVvalue; | ||||
|         mExposureCorrectionChanged = true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setFacing(@NonNull Facing facing) { | ||||
|         mFacing = facing; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setFlash(@NonNull Flash flash) { | ||||
|         mFlash = flash; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setWhiteBalance(@NonNull WhiteBalance whiteBalance) { | ||||
|         mWhiteBalance = whiteBalance; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setMode(@NonNull Mode mode) { | ||||
|         mMode = mode; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setHdr(@NonNull Hdr hdr) { | ||||
|         mHdr = hdr; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setAudio(@NonNull Audio audio) { | ||||
|         mAudio = audio; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setLocation(@Nullable Location location) { | ||||
|         mLocation = location; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void takePicture() { | ||||
|         mPictureCaptured = true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void takePictureSnapshot(@NonNull AspectRatio viewAspectRatio) { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void takeVideo(@NonNull File file) { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void takeVideoSnapshot(@NonNull File file, @NonNull AspectRatio viewAspectRatio) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void stopVideo() { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void startAutoFocus(@Nullable Gesture gesture, @NonNull PointF point) { | ||||
|         mFocusStarted = true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onSurfaceChanged() { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onSurfaceAvailable() { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onSurfaceDestroyed() { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onBufferAvailable(@NonNull byte[] buffer) { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void setPlaySounds(boolean playSounds) { | ||||
| 
 | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,175 @@ | ||||
| package com.otaliastudios.cameraview.engine; | ||||
| 
 | ||||
| 
 | ||||
| import android.graphics.PointF; | ||||
| import android.location.Location; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.CameraOptions; | ||||
| import com.otaliastudios.cameraview.PictureResult; | ||||
| import com.otaliastudios.cameraview.VideoResult; | ||||
| import com.otaliastudios.cameraview.controls.Audio; | ||||
| import com.otaliastudios.cameraview.controls.Facing; | ||||
| import com.otaliastudios.cameraview.controls.Flash; | ||||
| import com.otaliastudios.cameraview.engine.CameraEngine; | ||||
| import com.otaliastudios.cameraview.gesture.Gesture; | ||||
| import com.otaliastudios.cameraview.controls.Hdr; | ||||
| import com.otaliastudios.cameraview.controls.Mode; | ||||
| import com.otaliastudios.cameraview.controls.WhiteBalance; | ||||
| import com.otaliastudios.cameraview.size.AspectRatio; | ||||
| import com.otaliastudios.cameraview.size.Size; | ||||
| import com.otaliastudios.cameraview.size.SizeSelector; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.annotation.VisibleForTesting; | ||||
| 
 | ||||
| import java.io.File; | ||||
| 
 | ||||
| public class MockCameraEngine extends CameraEngine { | ||||
| 
 | ||||
|     public boolean mPictureCaptured; | ||||
|     public boolean mFocusStarted; | ||||
|     public boolean mZoomChanged; | ||||
|     public boolean mExposureCorrectionChanged; | ||||
| 
 | ||||
|     public MockCameraEngine(CameraEngine.Callback callback) { | ||||
|         super(callback); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onStart() { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onStop() { | ||||
|     } | ||||
| 
 | ||||
|     public void setMockCameraOptions(CameraOptions options) { | ||||
|         mCameraOptions = options; | ||||
|     } | ||||
| 
 | ||||
|     public void setMockPreviewStreamSize(Size size) { | ||||
|         mPreviewStreamSize = size; | ||||
|     } | ||||
| 
 | ||||
|     public void mockStarted(boolean started) { | ||||
|         mState = started ? STATE_STARTED : STATE_STOPPED; | ||||
|     } | ||||
| 
 | ||||
|     public int getSnapshotMaxWidth() { | ||||
|         return mSnapshotMaxWidth; | ||||
|     } | ||||
| 
 | ||||
|     public int getSnapshotMaxHeight() { | ||||
|         return mSnapshotMaxHeight; | ||||
|     } | ||||
| 
 | ||||
|     public SizeSelector getInternalPreviewStreamSizeSelector() { | ||||
|         return super.getPreviewStreamSizeSelector(); | ||||
|     } | ||||
| 
 | ||||
|     public SizeSelector getInternalPictureSizeSelector() { | ||||
|         return super.getPictureSizeSelector(); | ||||
|     } | ||||
| 
 | ||||
|     public SizeSelector getInternalVideoSizeSelector() { | ||||
|         return super.getVideoSizeSelector(); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @Override | ||||
|     public void setZoom(float zoom, @Nullable PointF[] points, boolean notify) { | ||||
|         mZoomValue = zoom; | ||||
|         mZoomChanged = true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setExposureCorrection(float EVvalue, @NonNull float[] bounds, @Nullable PointF[] points, boolean notify) { | ||||
|         mExposureCorrectionValue = EVvalue; | ||||
|         mExposureCorrectionChanged = true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setFacing(@NonNull Facing facing) { | ||||
|         mFacing = facing; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setFlash(@NonNull Flash flash) { | ||||
|         mFlash = flash; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setWhiteBalance(@NonNull WhiteBalance whiteBalance) { | ||||
|         mWhiteBalance = whiteBalance; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setMode(@NonNull Mode mode) { | ||||
|         mMode = mode; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setHdr(@NonNull Hdr hdr) { | ||||
|         mHdr = hdr; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setAudio(@NonNull Audio audio) { | ||||
|         mAudio = audio; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setLocation(@Nullable Location location) { | ||||
|         mLocation = location; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void takePicture(@NonNull PictureResult.Stub stub) { | ||||
|         mPictureCaptured = true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void takePictureSnapshot(@NonNull PictureResult.Stub stub, @NonNull AspectRatio viewAspectRatio) { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void takeVideo(@NonNull VideoResult.Stub stub, @NonNull File file) { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void takeVideoSnapshot(@NonNull VideoResult.Stub stub, @NonNull File file, @NonNull AspectRatio viewAspectRatio) { | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @Override | ||||
|     public void stopVideo() { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void startAutoFocus(@Nullable Gesture gesture, @NonNull PointF point) { | ||||
|         mFocusStarted = true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onSurfaceChanged() { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onSurfaceAvailable() { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onSurfaceDestroyed() { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onBufferAvailable(@NonNull byte[] buffer) { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setPlaySounds(boolean playSounds) { | ||||
| 
 | ||||
|     } | ||||
| } | ||||
| @ -1,6 +1,11 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| package com.otaliastudios.cameraview.internal; | ||||
| 
 | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| import com.otaliastudios.cameraview.TestActivity; | ||||
| import com.otaliastudios.cameraview.controls.Grid; | ||||
| import com.otaliastudios.cameraview.internal.GridLinesLayout; | ||||
| 
 | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.MediumTest; | ||||
| import androidx.test.rule.ActivityTestRule; | ||||
| @ -1,8 +1,13 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| package com.otaliastudios.cameraview.internal.utils; | ||||
| 
 | ||||
| 
 | ||||
| import android.graphics.Rect; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| import com.otaliastudios.cameraview.internal.utils.CropHelper; | ||||
| import com.otaliastudios.cameraview.size.AspectRatio; | ||||
| import com.otaliastudios.cameraview.size.Size; | ||||
| 
 | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| @ -1,10 +1,13 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| package com.otaliastudios.cameraview.internal.utils; | ||||
| 
 | ||||
| 
 | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| import android.view.OrientationEventListener; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| import com.otaliastudios.cameraview.internal.utils.OrientationHelper; | ||||
| 
 | ||||
| import org.junit.After; | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
| @ -1,6 +1,10 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| package com.otaliastudios.cameraview.internal.utils; | ||||
| 
 | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| import com.otaliastudios.cameraview.internal.utils.Task; | ||||
| import com.otaliastudios.cameraview.internal.utils.WorkerHandler; | ||||
| 
 | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.SmallTest; | ||||
| 
 | ||||
| @ -1,3 +0,0 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| @interface EncoderThread {} | ||||
| @ -1,11 +0,0 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| import android.media.MediaCodec; | ||||
| 
 | ||||
| import java.nio.ByteBuffer; | ||||
| 
 | ||||
| class OutputBuffer { | ||||
|     MediaCodec.BufferInfo info; | ||||
|     int trackIndex; | ||||
|     ByteBuffer data; | ||||
| } | ||||
| @ -1,89 +0,0 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| import java.util.concurrent.LinkedBlockingQueue; | ||||
| 
 | ||||
| import androidx.annotation.CallSuper; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| 
 | ||||
| class Pool<T> { | ||||
| 
 | ||||
|     private static final String TAG = Pool.class.getSimpleName(); | ||||
|     private static final CameraLogger LOG = CameraLogger.create(TAG); | ||||
| 
 | ||||
|     private int maxPoolSize; | ||||
|     private int activeCount; | ||||
|     private LinkedBlockingQueue<T> mQueue; | ||||
|     private Factory<T> factory; | ||||
| 
 | ||||
|     interface Factory<T> { | ||||
|         T create(); | ||||
|     } | ||||
| 
 | ||||
|     Pool(int maxPoolSize, Factory<T> factory) { | ||||
|         this.maxPoolSize = maxPoolSize; | ||||
|         this.mQueue = new LinkedBlockingQueue<>(maxPoolSize); | ||||
|         this.factory = factory; | ||||
|     } | ||||
| 
 | ||||
|     boolean canGet() { | ||||
|         return count() < maxPoolSize; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     T get() { | ||||
|         T buffer = mQueue.poll(); | ||||
|         if (buffer != null) { | ||||
|             activeCount++; // poll decreases, this fixes
 | ||||
|             LOG.v("GET: Reusing recycled item.", this); | ||||
|             return buffer; | ||||
|         } | ||||
| 
 | ||||
|         if (!canGet()) { | ||||
|             LOG.v("GET: Returning null. Too much items requested.", this); | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         activeCount++; | ||||
|         LOG.v("GET: Creating a new item.", this); | ||||
|         return factory.create(); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     void recycle(@NonNull T item) { | ||||
|         LOG.v("RECYCLE: Recycling item.", this); | ||||
|         if (--activeCount < 0) { | ||||
|             throw new IllegalStateException("Trying to recycle an item which makes activeCount < 0." + | ||||
|                     "This means that this or some previous items being recycled were not coming from " + | ||||
|                     "this pool, or some item was recycled more than once. " + this); | ||||
|         } | ||||
|         if (!mQueue.offer(item)) { | ||||
|             throw new IllegalStateException("Trying to recycle an item while the queue is full. " + | ||||
|                     "This means that this or some previous items being recycled were not coming from " + | ||||
|                     "this pool, or some item was recycled more than once. " + this); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public String toString() { | ||||
|         return getClass().getSimpleName() + " -- count:" + count() + ", active:" + activeCount() + ", cached:" + cachedCount(); | ||||
|     } | ||||
| 
 | ||||
|     final int count() { | ||||
|         return activeCount() + cachedCount(); | ||||
|     } | ||||
| 
 | ||||
|     final int activeCount() { | ||||
|         return activeCount; | ||||
|     } | ||||
| 
 | ||||
|     final int cachedCount() { | ||||
|         return mQueue.size(); | ||||
|     } | ||||
| 
 | ||||
|     @CallSuper | ||||
|     void clear() { | ||||
|         mQueue.clear(); | ||||
|     } | ||||
| } | ||||
| @ -1,7 +1,5 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| import android.graphics.Bitmap; | ||||
| 
 | ||||
| import java.io.File; | ||||
| 
 | ||||
| import androidx.annotation.Nullable; | ||||
| @ -1,19 +0,0 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| import android.hardware.Camera; | ||||
| import android.media.MediaRecorder; | ||||
| import android.os.Build; | ||||
| 
 | ||||
| import java.util.HashMap; | ||||
| 
 | ||||
| abstract class Mapper { | ||||
| 
 | ||||
|     abstract <T> T map(Flash flash); | ||||
|     abstract <T> T map(Facing facing); | ||||
|     abstract <T> T map(WhiteBalance whiteBalance); | ||||
|     abstract <T> T map(Hdr hdr); | ||||
|     abstract <T> Flash unmapFlash(T cameraConstant); | ||||
|     abstract <T> Facing unmapFacing(T cameraConstant); | ||||
|     abstract <T> WhiteBalance unmapWhiteBalance(T cameraConstant); | ||||
|     abstract <T> Hdr unmapHdr(T cameraConstant); | ||||
| } | ||||
| @ -1,85 +0,0 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| import android.hardware.Camera; | ||||
| import android.os.Build; | ||||
| 
 | ||||
| import java.util.HashMap; | ||||
| 
 | ||||
| 
 | ||||
| @SuppressWarnings("unchecked") | ||||
| class Mapper1 extends Mapper { | ||||
| 
 | ||||
|     private static final HashMap<Flash, String> FLASH = new HashMap<>(); | ||||
|     private static final HashMap<WhiteBalance, String> WB = new HashMap<>(); | ||||
|     private static final HashMap<Facing, Integer> FACING = new HashMap<>(); | ||||
|     private static final HashMap<Hdr, String> HDR = new HashMap<>(); | ||||
| 
 | ||||
|     static { | ||||
|         FLASH.put(Flash.OFF, Camera.Parameters.FLASH_MODE_OFF); | ||||
|         FLASH.put(Flash.ON, Camera.Parameters.FLASH_MODE_ON); | ||||
|         FLASH.put(Flash.AUTO, Camera.Parameters.FLASH_MODE_AUTO); | ||||
|         FLASH.put(Flash.TORCH, Camera.Parameters.FLASH_MODE_TORCH); | ||||
|         FACING.put(Facing.BACK, Camera.CameraInfo.CAMERA_FACING_BACK); | ||||
|         FACING.put(Facing.FRONT, Camera.CameraInfo.CAMERA_FACING_FRONT); | ||||
|         WB.put(WhiteBalance.AUTO, Camera.Parameters.WHITE_BALANCE_AUTO); | ||||
|         WB.put(WhiteBalance.INCANDESCENT, Camera.Parameters.WHITE_BALANCE_INCANDESCENT); | ||||
|         WB.put(WhiteBalance.FLUORESCENT, Camera.Parameters.WHITE_BALANCE_FLUORESCENT); | ||||
|         WB.put(WhiteBalance.DAYLIGHT, Camera.Parameters.WHITE_BALANCE_DAYLIGHT); | ||||
|         WB.put(WhiteBalance.CLOUDY, Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT); | ||||
|         HDR.put(Hdr.OFF, Camera.Parameters.SCENE_MODE_AUTO); | ||||
|         if (Build.VERSION.SDK_INT >= 17) { | ||||
|             HDR.put(Hdr.ON, Camera.Parameters.SCENE_MODE_HDR); | ||||
|         } else { | ||||
|             HDR.put(Hdr.ON, "hdr"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     <T> T map(Flash flash) { | ||||
|         return (T) FLASH.get(flash); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     <T> T map(Facing facing) { | ||||
|         return (T) FACING.get(facing); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     <T> T map(WhiteBalance whiteBalance) { | ||||
|         return (T) WB.get(whiteBalance); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     <T> T map(Hdr hdr) { | ||||
|         return (T) HDR.get(hdr); | ||||
|     } | ||||
| 
 | ||||
|     private <T> T reverseLookup(HashMap<T, ?> map, Object object) { | ||||
|         for (T value : map.keySet()) { | ||||
|             if (map.get(value).equals(object)) { | ||||
|                 return value; | ||||
|             } | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     <T> Flash unmapFlash(T cameraConstant) { | ||||
|         return reverseLookup(FLASH, cameraConstant); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     <T> Facing unmapFacing(T cameraConstant) { | ||||
|         return reverseLookup(FACING, cameraConstant); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     <T> WhiteBalance unmapWhiteBalance(T cameraConstant) { | ||||
|         return reverseLookup(WB, cameraConstant); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     <T> Hdr unmapHdr(T cameraConstant) { | ||||
|         return reverseLookup(HDR, cameraConstant); | ||||
|     } | ||||
| } | ||||
| @ -1,40 +0,0 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| 
 | ||||
| /** | ||||
|  * Interface for picture capturing. | ||||
|  * Don't call start if already started. Don't call stop if already stopped. | ||||
|  * Don't reuse. | ||||
|  */ | ||||
| abstract class PictureRecorder { | ||||
| 
 | ||||
|     /* tests */ PictureResult mResult; | ||||
|     /* tests */ PictureResultListener mListener; | ||||
| 
 | ||||
|     PictureRecorder(@NonNull PictureResult stub, @Nullable PictureResultListener listener) { | ||||
|         mResult = stub; | ||||
|         mListener = listener; | ||||
|     } | ||||
| 
 | ||||
|     abstract void take(); | ||||
| 
 | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     protected void dispatchOnShutter(boolean didPlaySound) { | ||||
|         if (mListener != null) mListener.onPictureShutter(didPlaySound); | ||||
|     } | ||||
| 
 | ||||
|     protected void dispatchResult() { | ||||
|         if (mListener != null) { | ||||
|             mListener.onPictureResult(mResult); | ||||
|             mListener = null; | ||||
|             mResult = null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     interface PictureResultListener { | ||||
|         void onPictureShutter(boolean didPlaySound); | ||||
|         void onPictureResult(@Nullable PictureResult result); | ||||
|     } | ||||
| } | ||||
| @ -1,40 +0,0 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| 
 | ||||
| /** | ||||
|  * Interface for video recording. | ||||
|  * Don't call start if already started. Don't call stop if already stopped. | ||||
|  * Don't reuse. | ||||
|  */ | ||||
| abstract class VideoRecorder { | ||||
| 
 | ||||
|     /* tests */ VideoResult mResult; | ||||
|     /* tests */ VideoResultListener mListener; | ||||
|     protected Exception mError; | ||||
| 
 | ||||
|     VideoRecorder(@NonNull VideoResult stub, @Nullable VideoResultListener listener) { | ||||
|         mResult = stub; | ||||
|         mListener = listener; | ||||
|     } | ||||
| 
 | ||||
|     abstract void start(); | ||||
| 
 | ||||
|     abstract void stop(); | ||||
| 
 | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     protected void dispatchResult() { | ||||
|         if (mListener != null) { | ||||
|             mListener.onVideoResult(mResult, mError); | ||||
|             mListener = null; | ||||
|             mResult = null; | ||||
|             mError = null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     interface VideoResultListener { | ||||
|         void onVideoResult(@Nullable VideoResult result, @Nullable Exception exception); | ||||
|     } | ||||
| } | ||||
| @ -1,6 +1,8 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| package com.otaliastudios.cameraview.controls; | ||||
| 
 | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.CameraView; | ||||
| 
 | ||||
| import androidx.annotation.Nullable; | ||||
| 
 | ||||
| /** | ||||
| @ -1,4 +1,4 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| package com.otaliastudios.cameraview.controls; | ||||
| 
 | ||||
| /** | ||||
|  * Base interface for controls like {@link Audio}, | ||||
| @ -0,0 +1,72 @@ | ||||
| package com.otaliastudios.cameraview.controls; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.content.res.TypedArray; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.R; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| 
 | ||||
| /** | ||||
|  * Parses controls from XML attributes. | ||||
|  */ | ||||
| public class ControlParser { | ||||
| 
 | ||||
|     private int preview; | ||||
|     private int facing; | ||||
|     private int flash; | ||||
|     private int grid; | ||||
|     private int whiteBalance; | ||||
|     private int mode; | ||||
|     private int hdr; | ||||
|     private int audio; | ||||
|     private int videoCodec; | ||||
| 
 | ||||
|     public ControlParser(@NonNull Context context, @NonNull TypedArray array) { | ||||
|         this.preview = array.getInteger(R.styleable.CameraView_cameraPreview, Preview.DEFAULT.value()); | ||||
|         this.facing = array.getInteger(R.styleable.CameraView_cameraFacing, Facing.DEFAULT(context).value()); | ||||
|         this.flash = array.getInteger(R.styleable.CameraView_cameraFlash, Flash.DEFAULT.value()); | ||||
|         this.grid = array.getInteger(R.styleable.CameraView_cameraGrid, Grid.DEFAULT.value()); | ||||
|         this.whiteBalance = array.getInteger(R.styleable.CameraView_cameraWhiteBalance, WhiteBalance.DEFAULT.value()); | ||||
|         this.mode = array.getInteger(R.styleable.CameraView_cameraMode, Mode.DEFAULT.value()); | ||||
|         this.hdr = array.getInteger(R.styleable.CameraView_cameraHdr, Hdr.DEFAULT.value()); | ||||
|         this.audio = array.getInteger(R.styleable.CameraView_cameraAudio, Audio.DEFAULT.value()); | ||||
|         this.videoCodec = array.getInteger(R.styleable.CameraView_cameraVideoCodec, VideoCodec.DEFAULT.value()); | ||||
|     } | ||||
| 
 | ||||
|     public Preview getPreview() { | ||||
|         return Preview.fromValue(preview); | ||||
|     } | ||||
| 
 | ||||
|     public Facing getFacing() { | ||||
|         return Facing.fromValue(facing); | ||||
|     } | ||||
| 
 | ||||
|     public Flash getFlash() { | ||||
|         return Flash.fromValue(flash); | ||||
|     } | ||||
| 
 | ||||
|     public Grid getGrid() { | ||||
|         return Grid.fromValue(grid); | ||||
|     } | ||||
| 
 | ||||
|     public Mode getMode() { | ||||
|         return Mode.fromValue(mode); | ||||
|     } | ||||
| 
 | ||||
|     public WhiteBalance getWhiteBalance() { | ||||
|         return WhiteBalance.fromValue(whiteBalance); | ||||
|     } | ||||
| 
 | ||||
|     public Hdr getHdr() { | ||||
|         return Hdr.fromValue(hdr); | ||||
|     } | ||||
| 
 | ||||
|     public Audio getAudio() { | ||||
|         return Audio.fromValue(audio); | ||||
|     } | ||||
| 
 | ||||
|     public VideoCodec getVideoCodec() { | ||||
|         return VideoCodec.fromValue(videoCodec); | ||||
|     } | ||||
| } | ||||
| @ -1,7 +1,11 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| package com.otaliastudios.cameraview.controls; | ||||
| 
 | ||||
| 
 | ||||
| import android.content.Context; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.CameraUtils; | ||||
| import com.otaliastudios.cameraview.CameraView; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| 
 | ||||
| @ -1,6 +1,9 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| package com.otaliastudios.cameraview.controls; | ||||
| 
 | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.CameraOptions; | ||||
| import com.otaliastudios.cameraview.CameraView; | ||||
| 
 | ||||
| import androidx.annotation.Nullable; | ||||
| 
 | ||||
| /** | ||||
| @ -1,6 +1,8 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| package com.otaliastudios.cameraview.controls; | ||||
| 
 | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.CameraView; | ||||
| 
 | ||||
| import androidx.annotation.Nullable; | ||||
| 
 | ||||
| /** | ||||
| @ -1,6 +1,8 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| package com.otaliastudios.cameraview.controls; | ||||
| 
 | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.CameraView; | ||||
| 
 | ||||
| import androidx.annotation.Nullable; | ||||
| 
 | ||||
| /** | ||||
| @ -1,6 +1,8 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| package com.otaliastudios.cameraview.controls; | ||||
| 
 | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.CameraView; | ||||
| 
 | ||||
| import androidx.annotation.Nullable; | ||||
| 
 | ||||
| import java.io.File; | ||||
| @ -1,6 +1,8 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| package com.otaliastudios.cameraview.controls; | ||||
| 
 | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.CameraView; | ||||
| 
 | ||||
| import androidx.annotation.Nullable; | ||||
| 
 | ||||
| /** | ||||
| @ -1,7 +1,8 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| package com.otaliastudios.cameraview.controls; | ||||
| 
 | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import com.otaliastudios.cameraview.CameraView; | ||||
| 
 | ||||
| import androidx.annotation.Nullable; | ||||
| 
 | ||||
| /** | ||||
| @ -1,6 +1,9 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| package com.otaliastudios.cameraview.controls; | ||||
| 
 | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.CameraOptions; | ||||
| import com.otaliastudios.cameraview.CameraView; | ||||
| 
 | ||||
| import androidx.annotation.Nullable; | ||||
| 
 | ||||
| /** | ||||
| @ -0,0 +1,123 @@ | ||||
| package com.otaliastudios.cameraview.engine; | ||||
| 
 | ||||
| import android.hardware.Camera; | ||||
| import android.os.Build; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.controls.Facing; | ||||
| import com.otaliastudios.cameraview.controls.Flash; | ||||
| import com.otaliastudios.cameraview.controls.Hdr; | ||||
| import com.otaliastudios.cameraview.controls.WhiteBalance; | ||||
| 
 | ||||
| import java.util.HashMap; | ||||
| 
 | ||||
| /** | ||||
|  * A Mapper maps camera engine constants to CameraView constants. | ||||
|  */ | ||||
| public abstract class Mapper { | ||||
| 
 | ||||
|     private Mapper() {} | ||||
| 
 | ||||
|     public abstract <T> T map(Flash flash); | ||||
| 
 | ||||
|     public abstract <T> T map(Facing facing); | ||||
| 
 | ||||
|     public abstract <T> T map(WhiteBalance whiteBalance); | ||||
| 
 | ||||
|     public abstract <T> T map(Hdr hdr); | ||||
| 
 | ||||
|     public abstract <T> Flash unmapFlash(T cameraConstant); | ||||
| 
 | ||||
|     public abstract <T> Facing unmapFacing(T cameraConstant); | ||||
| 
 | ||||
|     public abstract <T> WhiteBalance unmapWhiteBalance(T cameraConstant); | ||||
| 
 | ||||
|     public abstract <T> Hdr unmapHdr(T cameraConstant); | ||||
| 
 | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     protected <T> T reverseLookup(HashMap<T, ?> map, Object object) { | ||||
|         for (T value : map.keySet()) { | ||||
|             if (object.equals(map.get(value))) { | ||||
|                 return value; | ||||
|             } | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     private static Mapper CAMERA1; | ||||
| 
 | ||||
|     public static Mapper get() { | ||||
|         if (CAMERA1 == null) { | ||||
|             CAMERA1 = new Camera1Mapper(); | ||||
|         } | ||||
|         return CAMERA1; | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings("unchecked") | ||||
|     private static class Camera1Mapper extends Mapper { | ||||
| 
 | ||||
|         private static final HashMap<Flash, String> FLASH = new HashMap<>(); | ||||
|         private static final HashMap<WhiteBalance, String> WB = new HashMap<>(); | ||||
|         private static final HashMap<Facing, Integer> FACING = new HashMap<>(); | ||||
|         private static final HashMap<Hdr, String> HDR = new HashMap<>(); | ||||
| 
 | ||||
|         static { | ||||
|             FLASH.put(Flash.OFF, Camera.Parameters.FLASH_MODE_OFF); | ||||
|             FLASH.put(Flash.ON, Camera.Parameters.FLASH_MODE_ON); | ||||
|             FLASH.put(Flash.AUTO, Camera.Parameters.FLASH_MODE_AUTO); | ||||
|             FLASH.put(Flash.TORCH, Camera.Parameters.FLASH_MODE_TORCH); | ||||
|             FACING.put(Facing.BACK, Camera.CameraInfo.CAMERA_FACING_BACK); | ||||
|             FACING.put(Facing.FRONT, Camera.CameraInfo.CAMERA_FACING_FRONT); | ||||
|             WB.put(WhiteBalance.AUTO, Camera.Parameters.WHITE_BALANCE_AUTO); | ||||
|             WB.put(WhiteBalance.INCANDESCENT, Camera.Parameters.WHITE_BALANCE_INCANDESCENT); | ||||
|             WB.put(WhiteBalance.FLUORESCENT, Camera.Parameters.WHITE_BALANCE_FLUORESCENT); | ||||
|             WB.put(WhiteBalance.DAYLIGHT, Camera.Parameters.WHITE_BALANCE_DAYLIGHT); | ||||
|             WB.put(WhiteBalance.CLOUDY, Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT); | ||||
|             HDR.put(Hdr.OFF, Camera.Parameters.SCENE_MODE_AUTO); | ||||
|             if (Build.VERSION.SDK_INT >= 17) { | ||||
|                 HDR.put(Hdr.ON, Camera.Parameters.SCENE_MODE_HDR); | ||||
|             } else { | ||||
|                 HDR.put(Hdr.ON, "hdr"); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public <T> T map(Flash flash) { | ||||
|             return (T) FLASH.get(flash); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public <T> T map(Facing facing) { | ||||
|             return (T) FACING.get(facing); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public <T> T map(WhiteBalance whiteBalance) { | ||||
|             return (T) WB.get(whiteBalance); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public <T> T map(Hdr hdr) { | ||||
|             return (T) HDR.get(hdr); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public <T> Flash unmapFlash(T cameraConstant) { | ||||
|             return reverseLookup(FLASH, cameraConstant); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public <T> Facing unmapFacing(T cameraConstant) { | ||||
|             return reverseLookup(FACING, cameraConstant); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public <T> WhiteBalance unmapWhiteBalance(T cameraConstant) { | ||||
|             return reverseLookup(WB, cameraConstant); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public <T> Hdr unmapHdr(T cameraConstant) { | ||||
|             return reverseLookup(HDR, cameraConstant); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,4 +1,6 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| package com.otaliastudios.cameraview.frame; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.CameraView; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.WorkerThread; | ||||
| @ -0,0 +1,159 @@ | ||||
| package com.otaliastudios.cameraview.gesture; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.graphics.PointF; | ||||
| import androidx.annotation.NonNull; | ||||
| import android.view.MotionEvent; | ||||
| import android.widget.FrameLayout; | ||||
| 
 | ||||
| /** | ||||
|  * Base class for gesture layouts, that is, layouts that will capture | ||||
|  * gestures. | ||||
|  */ | ||||
| public abstract class GestureLayout extends FrameLayout { | ||||
| 
 | ||||
|     // The number of possible values between minValue and maxValue, for the getValue method.
 | ||||
|     // We could make this non-static (e.g. larger granularity for exposure correction).
 | ||||
|     private final static int GRANULARITY = 50; | ||||
|     private boolean mActive; | ||||
|     private Gesture mType; | ||||
|     private PointF[] mPoints; | ||||
| 
 | ||||
|     GestureLayout(@NonNull Context context, int points) { | ||||
|         super(context); | ||||
|         mPoints = new PointF[points]; | ||||
|         for (int i = 0; i < points; i++) { | ||||
|             mPoints[i] = new PointF(0, 0); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Makes this instance active, which means, listening to events. | ||||
|      * @param active whether this should be active or not | ||||
|      */ | ||||
|     public void setActive(boolean active) { | ||||
|         mActive = active; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Whether this instance is active, which means, it is listening | ||||
|      * to events and identifying new gestures. | ||||
|      * @return true if active | ||||
|      */ | ||||
|     public boolean isActive() { | ||||
|         return mActive; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Called when new events are available. | ||||
|      * If true is returned, users will call {@link #getGesture()}, {@link #getPoints()} | ||||
|      * and maybe {@link #getValue(float, float, float)} to know more about the gesture. | ||||
|      * | ||||
|      * @param event the new event | ||||
|      * @return true if a gesture was detected | ||||
|      */ | ||||
|     public final boolean onTouchEvent(@NonNull MotionEvent event) { | ||||
|         if (!mActive) return false; | ||||
|         return handleTouchEvent(event); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Called when new events are available. | ||||
|      * If true is returned, users will call {@link #getGesture()}, {@link #getPoints()} | ||||
|      * and maybe {@link #getValue(float, float, float)} to know more about the gesture. | ||||
|      * | ||||
|      * @param event the new event | ||||
|      * @return true if a gesture was detected | ||||
|      */ | ||||
|     protected abstract boolean handleTouchEvent(@NonNull MotionEvent event); | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the gesture that this instance is currently detecting. | ||||
|      * This is mutable - for instance, a scroll layout can detect both | ||||
|      * horizontal and vertical scroll gestures. | ||||
|      * | ||||
|      * @return the current gesture | ||||
|      */ | ||||
|     @NonNull | ||||
|     public final Gesture getGesture() { | ||||
|         return mType; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sets the currently detected gesture. | ||||
|      * @see #getGesture() | ||||
|      * | ||||
|      * @param gesture the current gesture | ||||
|      */ | ||||
|     protected final void setGesture(Gesture gesture) { | ||||
|         mType = gesture; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns an array of points that identify the currently | ||||
|      * detected gesture. If no gesture was detected, this returns | ||||
|      * an array of points with x and y set to 0. | ||||
|      * | ||||
|      * @return array of gesture points | ||||
|      */ | ||||
|     @NonNull | ||||
|     public final PointF[] getPoints() { | ||||
|         return mPoints; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Utility function to access an item in the | ||||
|      * {@link #getPoints()} array. | ||||
|      * | ||||
|      * @param which the array position | ||||
|      * @return the point | ||||
|      */ | ||||
|     @NonNull | ||||
|     protected final PointF getPoint(int which) { | ||||
|         return mPoints[which]; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * For {@link GestureType#CONTINUOUS} gestures, returns the float value at the current | ||||
|      * gesture state. This means, for example, scaling the old value with a pinch factor, | ||||
|      * taking into account the minimum and maximum values. | ||||
|      * | ||||
|      * @param currValue the last value | ||||
|      * @param minValue the min possible value | ||||
|      * @param maxValue the max possible value | ||||
|      * @return the new continuous value | ||||
|      */ | ||||
|     public final float computeValue(float currValue, float minValue, float maxValue) { | ||||
|         return capValue(currValue, getValue(currValue, minValue, maxValue), minValue, maxValue); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * For {@link GestureType#CONTINUOUS} gestures, returns the float value at the current | ||||
|      * gesture state. This means, for example, scaling the old value with a pinch factor, | ||||
|      * taking into account the minimum and maximum values. | ||||
|      * | ||||
|      * @param currValue the last value | ||||
|      * @param minValue the min possible value | ||||
|      * @param maxValue the max possible value | ||||
|      * @return the new continuous value | ||||
|      */ | ||||
|     protected abstract float getValue(float currValue, float minValue, float maxValue); | ||||
| 
 | ||||
|     /** | ||||
|      * Checks for newValue to be between minValue and maxValue, | ||||
|      * and checks that it is 'far enough' from the oldValue, in order | ||||
|      * to reduce useless updates. | ||||
|      */ | ||||
|     private static float capValue(float oldValue, float newValue, float minValue, float maxValue) { | ||||
|         if (newValue < minValue) newValue = minValue; | ||||
|         if (newValue > maxValue) newValue = maxValue; | ||||
| 
 | ||||
|         float distance = (maxValue - minValue) / (float) GRANULARITY; | ||||
|         float half = distance / 2; | ||||
|         if (newValue >= oldValue - half && newValue <= oldValue + half) { | ||||
|             // Too close! Return the oldValue.
 | ||||
|             return oldValue; | ||||
|         } | ||||
|         return newValue; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,52 @@ | ||||
| package com.otaliastudios.cameraview.gesture; | ||||
| 
 | ||||
| import android.content.res.TypedArray; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.R; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| 
 | ||||
| /** | ||||
|  * Parses gestures from XML attributes. | ||||
|  */ | ||||
| public class GestureParser { | ||||
| 
 | ||||
|     private int tapAction; | ||||
|     private int longTapAction; | ||||
|     private int pinchAction; | ||||
|     private int horizontalScrollAction; | ||||
|     private int verticalScrollAction; | ||||
| 
 | ||||
|     public GestureParser(@NonNull TypedArray array) { | ||||
|         this.tapAction = array.getInteger(R.styleable.CameraView_cameraGestureTap, GestureAction.DEFAULT_TAP.value()); | ||||
|         this.longTapAction = array.getInteger(R.styleable.CameraView_cameraGestureLongTap, GestureAction.DEFAULT_LONG_TAP.value()); | ||||
|         this.pinchAction = array.getInteger(R.styleable.CameraView_cameraGesturePinch, GestureAction.DEFAULT_PINCH.value()); | ||||
|         this.horizontalScrollAction = array.getInteger(R.styleable.CameraView_cameraGestureScrollHorizontal, GestureAction.DEFAULT_SCROLL_HORIZONTAL.value()); | ||||
|         this.verticalScrollAction = array.getInteger(R.styleable.CameraView_cameraGestureScrollVertical, GestureAction.DEFAULT_SCROLL_VERTICAL.value()); | ||||
|     } | ||||
| 
 | ||||
|     private GestureAction get(int which) { | ||||
|         return GestureAction.fromValue(which); | ||||
|     } | ||||
| 
 | ||||
|     public GestureAction getTapAction() { | ||||
|         return get(tapAction); | ||||
|     } | ||||
| 
 | ||||
|     public GestureAction getLongTapAction() { | ||||
|         return get(longTapAction); | ||||
|     } | ||||
| 
 | ||||
|     public GestureAction getPinchAction() { | ||||
|         return get(pinchAction); | ||||
|     } | ||||
| 
 | ||||
|     public GestureAction getHorizontalScrollAction() { | ||||
|         return get(horizontalScrollAction); | ||||
|     } | ||||
| 
 | ||||
|     public GestureAction getVerticalScrollAction() { | ||||
|         return get(verticalScrollAction); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,23 @@ | ||||
| package com.otaliastudios.cameraview.gesture; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * Gestures and gesture actions can both have a type. For a gesture to be able to be mapped to | ||||
|  * a certain {@link GestureAction}, both of them might be of the same type. | ||||
|  */ | ||||
| public enum GestureType { | ||||
| 
 | ||||
|     /** | ||||
|      * Defines gestures or gesture actions that consist of a single operation. | ||||
|      * Gesture example: a tap. | ||||
|      * Gesture action example: taking a picture. | ||||
|      */ | ||||
|     ONE_SHOT, | ||||
| 
 | ||||
|     /** | ||||
|      * Defines gestures or gesture actions that consist of a continuous operation. | ||||
|      * Gesture example: pinching. | ||||
|      * Gesture action example: controlling zoom. | ||||
|      */ | ||||
|     CONTINUOUS | ||||
| } | ||||
| @ -1,13 +1,20 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| package com.otaliastudios.cameraview.internal.utils; | ||||
| 
 | ||||
| import android.graphics.Rect; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.size.AspectRatio; | ||||
| import com.otaliastudios.cameraview.size.Size; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| 
 | ||||
| class CropHelper { | ||||
| /** | ||||
|  * Simply computes the crop between a full size and a desired aspect ratio. | ||||
|  */ | ||||
| public class CropHelper { | ||||
| 
 | ||||
|     // It's important that size and aspect ratio belong to the same reference.
 | ||||
|     @NonNull | ||||
|     static Rect computeCrop(@NonNull Size currentSize, @NonNull AspectRatio targetRatio) { | ||||
|     public static Rect computeCrop(@NonNull Size currentSize, @NonNull AspectRatio targetRatio) { | ||||
|         int currentWidth = currentSize.getWidth(); | ||||
|         int currentHeight = currentSize.getHeight(); | ||||
|         if (targetRatio.matches(currentSize)) { | ||||
| @ -0,0 +1,38 @@ | ||||
| package com.otaliastudios.cameraview.internal.utils; | ||||
| 
 | ||||
| import androidx.exifinterface.media.ExifInterface; | ||||
| 
 | ||||
| /** | ||||
|  * Super basic exif utilities. | ||||
|  */ | ||||
| public class ExifHelper { | ||||
| 
 | ||||
|     /** | ||||
|      * Maps an {@link ExifInterface} orientation value | ||||
|      * to the actual degrees. | ||||
|      */ | ||||
|     public static int readExifOrientation(int exifOrientation) { | ||||
|         int orientation; | ||||
|         switch (exifOrientation) { | ||||
|             case ExifInterface.ORIENTATION_NORMAL: | ||||
|             case ExifInterface.ORIENTATION_FLIP_HORIZONTAL: | ||||
|                 orientation = 0; break; | ||||
| 
 | ||||
|             case ExifInterface.ORIENTATION_ROTATE_180: | ||||
|             case ExifInterface.ORIENTATION_FLIP_VERTICAL: | ||||
|                 orientation = 180; break; | ||||
| 
 | ||||
|             case ExifInterface.ORIENTATION_ROTATE_90: | ||||
|             case ExifInterface.ORIENTATION_TRANSPOSE: | ||||
|                 orientation = 90; break; | ||||
| 
 | ||||
|             case ExifInterface.ORIENTATION_ROTATE_270: | ||||
|             case ExifInterface.ORIENTATION_TRANSVERSE: | ||||
|                 orientation = 270; break; | ||||
| 
 | ||||
|             default: orientation = 0; | ||||
|         } | ||||
|         return orientation; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -0,0 +1,145 @@ | ||||
| package com.otaliastudios.cameraview.internal.utils; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.CameraLogger; | ||||
| 
 | ||||
| import java.util.concurrent.LinkedBlockingQueue; | ||||
| 
 | ||||
| import androidx.annotation.CallSuper; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| 
 | ||||
| /** | ||||
|  * Base class for pools of recycleable objects. | ||||
|  * @param <T> the object type | ||||
|  */ | ||||
| public class Pool<T> { | ||||
| 
 | ||||
|     private static final String TAG = Pool.class.getSimpleName(); | ||||
|     private static final CameraLogger LOG = CameraLogger.create(TAG); | ||||
| 
 | ||||
|     private int maxPoolSize; | ||||
|     private int activeCount; | ||||
|     private LinkedBlockingQueue<T> mQueue; | ||||
|     private Factory<T> factory; | ||||
| 
 | ||||
|     /** | ||||
|      * Used to create new instances of objects when needed. | ||||
|      * @param <T> object type | ||||
|      */ | ||||
|     public interface Factory<T> { | ||||
|         T create(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Creates a new pool with the given pool size and factory. | ||||
|      * @param maxPoolSize the max pool size | ||||
|      * @param factory the factory | ||||
|      */ | ||||
|     public Pool(int maxPoolSize, @NonNull Factory<T> factory) { | ||||
|         this.maxPoolSize = maxPoolSize; | ||||
|         this.mQueue = new LinkedBlockingQueue<>(maxPoolSize); | ||||
|         this.factory = factory; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Whether the pool is empty. This means that {@link #get()} will return | ||||
|      * a null item, because all objects were reclaimed and not recycled yet. | ||||
|      * | ||||
|      * @return whether the pool is empty | ||||
|      */ | ||||
|     public boolean isEmpty() { | ||||
|         return count() >= maxPoolSize; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns a new item, from the recycled pool if possible (if there are recycled items), | ||||
|      * or instantiating one through the factory (if we can respect the pool size). | ||||
|      * If these conditions are not met, this returns null. | ||||
|      * | ||||
|      * @return an item or null | ||||
|      */ | ||||
|     @Nullable | ||||
|     public T get() { | ||||
|         T item = mQueue.poll(); | ||||
|         if (item != null) { | ||||
|             activeCount++; // poll decreases, this fixes
 | ||||
|             LOG.v("GET: Reusing recycled item.", this); | ||||
|             return item; | ||||
|         } | ||||
| 
 | ||||
|         if (isEmpty()) { | ||||
|             LOG.v("GET: Returning null. Too much items requested.", this); | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         activeCount++; | ||||
|         LOG.v("GET: Creating a new item.", this); | ||||
|         return factory.create(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Recycles an item after it has been used. The item should come from a previous | ||||
|      * {@link #get()} call. | ||||
|      * | ||||
|      * @param item used item | ||||
|      */ | ||||
|     public void recycle(@NonNull T item) { | ||||
|         LOG.v("RECYCLE: Recycling item.", this); | ||||
|         if (--activeCount < 0) { | ||||
|             throw new IllegalStateException("Trying to recycle an item which makes activeCount < 0." + | ||||
|                     "This means that this or some previous items being recycled were not coming from " + | ||||
|                     "this pool, or some item was recycled more than once. " + this); | ||||
|         } | ||||
|         if (!mQueue.offer(item)) { | ||||
|             throw new IllegalStateException("Trying to recycle an item while the queue is full. " + | ||||
|                     "This means that this or some previous items being recycled were not coming from " + | ||||
|                     "this pool, or some item was recycled more than once. " + this); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Clears the pool of recycled items. | ||||
|      */ | ||||
|     @CallSuper | ||||
|     public void clear() { | ||||
|         mQueue.clear(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the count of all items managed by this pool. Includes | ||||
|      * - active items: currently being used | ||||
|      * - recycled items: used and recycled, available for second use | ||||
|      * | ||||
|      * @return count | ||||
|      */ | ||||
|     public final int count() { | ||||
|         return activeCount() + recycledCount(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the active items managed by this pools, which means, items | ||||
|      * currently being used. | ||||
|      * | ||||
|      * @return active count | ||||
|      */ | ||||
|     public final int activeCount() { | ||||
|         return activeCount; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the recycled items managed by this pool, which means, items | ||||
|      * that were used and later recycled, and are currently available for | ||||
|      * second use. | ||||
|      * | ||||
|      * @return recycled count | ||||
|      */ | ||||
|     public final int recycledCount() { | ||||
|         return mQueue.size(); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public String toString() { | ||||
|         return getClass().getSimpleName() + " -- count:" + count() + ", active:" + activeCount() + ", recycled:" + recycledCount(); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,111 @@ | ||||
| package com.otaliastudios.cameraview.internal.utils; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| 
 | ||||
| import java.util.concurrent.CountDownLatch; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| 
 | ||||
| /** | ||||
|  * A naive implementation of {@link java.util.concurrent.CountDownLatch} | ||||
|  * to help in testing. | ||||
|  */ | ||||
| public class Task<T> { | ||||
| 
 | ||||
|     private CountDownLatch mLatch; | ||||
|     private T mResult; | ||||
|     private int mCount; | ||||
| 
 | ||||
|     /** | ||||
|      * Creates an empty task. | ||||
|      * | ||||
|      * Listeners should: | ||||
|      * - call {@link #listen()} to notify they are interested in the next action | ||||
|      * - call {@link #await()} to know when the action is performed. | ||||
|      * | ||||
|      * Task owners should: | ||||
|      * - call {@link #start()} when task started | ||||
|      * - call {@link #end(Object)} when task ends | ||||
|      */ | ||||
|     public Task() { } | ||||
| 
 | ||||
|     /** | ||||
|      * Creates an empty task and starts listening. | ||||
|      * @param startListening whether to call listen | ||||
|      */ | ||||
|     public Task(boolean startListening) { | ||||
|         if (startListening) listen(); | ||||
|     } | ||||
| 
 | ||||
|     private boolean isListening() { | ||||
|         return mLatch != null; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Task owner method: notifies the action started. | ||||
|      */ | ||||
|     public void start() { | ||||
|         if (!isListening()) mCount++; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Task owner method: notifies the action ended. | ||||
|      * @param result the action result | ||||
|      */ | ||||
|     public void end(T result) { | ||||
|         if (mCount > 0) { | ||||
|             mCount--; | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (isListening()) { // Should be always true.
 | ||||
|             mResult = result; | ||||
|             mLatch.countDown(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Listener method: notifies we are interested in the next action. | ||||
|      */ | ||||
|     public void listen() { | ||||
|         if (isListening()) throw new RuntimeException("Should not happen."); | ||||
|         mResult = null; | ||||
|         mLatch = new CountDownLatch(1); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Listener method: waits for next task action to end. | ||||
|      * @param millis milliseconds | ||||
|      * @return the action result | ||||
|      */ | ||||
|     public T await(long millis) { | ||||
|         return await(millis, TimeUnit.MILLISECONDS); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Listener method: waits 1 minute for next task action to end. | ||||
|      * @return the action result | ||||
|      */ | ||||
|     public T await() { | ||||
|         return await(1, TimeUnit.MINUTES); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Listener method: waits for next task action to end. | ||||
|      * @param time time | ||||
|      * @param unit the time unit | ||||
|      * @return the action result | ||||
|      */ | ||||
|     private T await(long time, @NonNull TimeUnit unit) { | ||||
|         try { | ||||
|             mLatch.await(time, unit); | ||||
|         } catch (Exception e) { | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|         T result = mResult; | ||||
|         mResult = null; | ||||
|         mLatch = null; | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,75 @@ | ||||
| package com.otaliastudios.cameraview.picture; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.PictureResult; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.annotation.VisibleForTesting; | ||||
| 
 | ||||
| /** | ||||
|  * Interface for picture capturing. | ||||
|  * Don't call start if already started. Don't call stop if already stopped. | ||||
|  * Don't reuse. | ||||
|  */ | ||||
| public abstract class PictureRecorder { | ||||
| 
 | ||||
|     /** | ||||
|      * Listens for picture recorder events. | ||||
|      */ | ||||
|     public interface PictureResultListener { | ||||
| 
 | ||||
|         /** | ||||
|          * The shutter was activated. | ||||
|          * @param didPlaySound whether a sound was played | ||||
|          */ | ||||
|         void onPictureShutter(boolean didPlaySound); | ||||
| 
 | ||||
|         /** | ||||
|          * Picture was taken or there was some error, if | ||||
|          * the result is null. | ||||
|          * @param result the result or null if there was some error. | ||||
|          */ | ||||
|         void onPictureResult(@Nullable PictureResult.Stub result); | ||||
|     } | ||||
| 
 | ||||
|     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) PictureResult.Stub mResult; | ||||
|     @VisibleForTesting PictureResultListener mListener; | ||||
| 
 | ||||
|     /** | ||||
|      * Creates a new picture recorder. | ||||
|      * @param stub a picture stub | ||||
|      * @param listener a listener | ||||
|      */ | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     public PictureRecorder(@NonNull PictureResult.Stub stub, @Nullable PictureResultListener listener) { | ||||
|         mResult = stub; | ||||
|         mListener = listener; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Takes a picture. | ||||
|      */ | ||||
|     public abstract void take(); | ||||
| 
 | ||||
|     /** | ||||
|      * Subclasses can call this to notify that the shutter was activated, | ||||
|      * and whether it did play some sound or not. | ||||
|      * @param didPlaySound whether it played sounds | ||||
|      */ | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     protected void dispatchOnShutter(boolean didPlaySound) { | ||||
|         if (mListener != null) mListener.onPictureShutter(didPlaySound); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Subclasses can call this to notify that the result was obtained, | ||||
|      * either with some error (null result) or with the actual stub, filled. | ||||
|      */ | ||||
|     protected void dispatchResult() { | ||||
|         if (mListener != null) { | ||||
|             mListener.onPictureResult(mResult); | ||||
|             mListener = null; | ||||
|             mResult = null; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,266 @@ | ||||
| package com.otaliastudios.cameraview.preview; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.annotation.VisibleForTesting; | ||||
| 
 | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.CameraLogger; | ||||
| import com.otaliastudios.cameraview.engine.CameraEngine; | ||||
| import com.otaliastudios.cameraview.internal.utils.Task; | ||||
| import com.otaliastudios.cameraview.size.Size; | ||||
| 
 | ||||
| /** | ||||
|  * A CameraPreview takes in input stream from the {@link CameraEngine}, and streams it | ||||
|  * into an output surface that belongs to the view hierarchy. | ||||
|  * | ||||
|  * @param <T> the type of view which hosts the content surface | ||||
|  * @param <Output> the type of output, either {@link android.view.SurfaceHolder} or {@link android.graphics.SurfaceTexture} | ||||
|  */ | ||||
| public abstract class CameraPreview<T extends View, Output> { | ||||
| 
 | ||||
|     protected final static CameraLogger LOG = CameraLogger.create(CameraPreview.class.getSimpleName()); | ||||
| 
 | ||||
|     /** | ||||
|      * This is used to notify CameraEngine to recompute its camera Preview size. | ||||
|      * After that, CameraView will need a new layout pass to adapt to the Preview size. | ||||
|      */ | ||||
|     public interface SurfaceCallback { | ||||
| 
 | ||||
|         /** | ||||
|          * Called when the surface is available. | ||||
|          */ | ||||
|         void onSurfaceAvailable(); | ||||
| 
 | ||||
|         /** | ||||
|          * Called when the surface has changed. | ||||
|          */ | ||||
|         void onSurfaceChanged(); | ||||
| 
 | ||||
|         /** | ||||
|          * Called when the surface was destroyed. | ||||
|          */ | ||||
|         void onSurfaceDestroyed(); | ||||
|     } | ||||
| 
 | ||||
|     @VisibleForTesting Task<Void> mCropTask = new Task<>(); | ||||
|     private SurfaceCallback mSurfaceCallback; | ||||
|     private T mView; | ||||
|     boolean mCropping; | ||||
| 
 | ||||
|     // These are the surface dimensions in REF_VIEW.
 | ||||
|     int mOutputSurfaceWidth; | ||||
|     int mOutputSurfaceHeight; | ||||
| 
 | ||||
|     // These are the preview stream dimensions, in REF_VIEW.
 | ||||
|     int mInputStreamWidth; | ||||
|     int mInputStreamHeight; | ||||
| 
 | ||||
|     /** | ||||
|      * Creates a new preview. | ||||
|      * @param context a context | ||||
|      * @param parent where to inflate our view | ||||
|      * @param callback the callback | ||||
|      */ | ||||
|     public CameraPreview(@NonNull Context context, @NonNull ViewGroup parent, @Nullable SurfaceCallback callback) { | ||||
|         mView = onCreateView(context, parent); | ||||
|         mSurfaceCallback = callback; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sets a callback to be notified of surface events (creation, change, destruction) | ||||
|      * @param callback a callback | ||||
|      */ | ||||
|     public final void setSurfaceCallback(@NonNull SurfaceCallback callback) { | ||||
|         mSurfaceCallback = callback; | ||||
|         // If surface already available, dispatch.
 | ||||
|         if (mOutputSurfaceWidth != 0 || mOutputSurfaceHeight != 0) { | ||||
|             mSurfaceCallback.onSurfaceAvailable(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Called at creation time. Implementors should inflate the hierarchy into the | ||||
|      * parent ViewGroup, and return the View that actually hosts the surface. | ||||
|      * | ||||
|      * @param context a context | ||||
|      * @param parent where to inflate | ||||
|      * @return the view hosting the Surface | ||||
|      */ | ||||
|     @NonNull | ||||
|     protected abstract T onCreateView(@NonNull Context context, @NonNull ViewGroup parent); | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the view hosting the Surface. | ||||
|      * @return the view | ||||
|      */ | ||||
|     @NonNull | ||||
|     public final T getView() { | ||||
|         return mView; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * For testing purposes, should return the root view that was inflated into the | ||||
|      * parent during {@link #onCreateView(Context, ViewGroup)}. | ||||
|      * @return the root view | ||||
|      */ | ||||
|     @SuppressWarnings("unused") | ||||
|     @VisibleForTesting | ||||
|     @NonNull | ||||
|     abstract View getRootView(); | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the output surface object (for example a SurfaceHolder | ||||
|      * or a SurfaceTexture). | ||||
|      * @return the surface object | ||||
|      */ | ||||
|     @NonNull | ||||
|     public abstract Output getOutput(); | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the type of the output returned by {@link #getOutput()}. | ||||
|      * @return the output type | ||||
|      */ | ||||
|     @NonNull | ||||
|     public abstract Class<Output> getOutputClass(); | ||||
| 
 | ||||
|     /** | ||||
|      * Called to notify the preview of the input stream size. The width and height must be | ||||
|      * rotated before calling this, if needed, to be consistent with the VIEW reference. | ||||
|      * | ||||
|      * @param width width of the preview stream, in view coordinates | ||||
|      * @param height height of the preview stream, in view coordinates | ||||
|      */ | ||||
|     public void setStreamSize(int width, int height) { | ||||
|         LOG.i("setStreamSize:", "desiredW=", width, "desiredH=", height); | ||||
|         mInputStreamWidth = width; | ||||
|         mInputStreamHeight = height; | ||||
|         if (mInputStreamWidth > 0 && mInputStreamHeight > 0) { | ||||
|             crop(mCropTask); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the current input stream size, in view coordinates. | ||||
|      * @return the current input stream size | ||||
|      */ | ||||
|     @SuppressWarnings("unused") | ||||
|     @NonNull | ||||
|     final Size getStreamSize() { | ||||
|         return new Size(mInputStreamWidth, mInputStreamHeight); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the current output surface size, in view coordinates. | ||||
|      * @return the current output surface size. | ||||
|      */ | ||||
|     @NonNull | ||||
|     public final Size getSurfaceSize() { | ||||
|         return new Size(mOutputSurfaceWidth, mOutputSurfaceHeight); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Whether we have a valid surface already. | ||||
|      * @return whether we have a surface | ||||
|      */ | ||||
|     public final boolean hasSurface() { | ||||
|         return mOutputSurfaceWidth > 0 && mOutputSurfaceHeight > 0; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Subclasses can call this to notify that the surface is available. | ||||
|      * @param width surface width | ||||
|      * @param height surface height | ||||
|      */ | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     protected final void dispatchOnSurfaceAvailable(int width, int height) { | ||||
|         LOG.i("dispatchOnSurfaceAvailable:", "w=", width, "h=", height); | ||||
|         mOutputSurfaceWidth = width; | ||||
|         mOutputSurfaceHeight = height; | ||||
|         if (mOutputSurfaceWidth > 0 && mOutputSurfaceHeight > 0) { | ||||
|             crop(mCropTask); | ||||
|         } | ||||
|         mSurfaceCallback.onSurfaceAvailable(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Subclasses can call this to notify that the surface has changed. | ||||
|      * @param width surface width | ||||
|      * @param height surface height | ||||
|      */ | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     protected final void dispatchOnSurfaceSizeChanged(int width, int height) { | ||||
|         LOG.i("dispatchOnSurfaceSizeChanged:", "w=", width, "h=", height); | ||||
|         if (width != mOutputSurfaceWidth || height != mOutputSurfaceHeight) { | ||||
|             mOutputSurfaceWidth = width; | ||||
|             mOutputSurfaceHeight = height; | ||||
|             if (width > 0 && height > 0) { | ||||
|                 crop(mCropTask); | ||||
|             } | ||||
|             mSurfaceCallback.onSurfaceChanged(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Subclasses can call this to notify that the surface has been destroyed. | ||||
|      */ | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     protected final void dispatchOnSurfaceDestroyed() { | ||||
|         mOutputSurfaceWidth = 0; | ||||
|         mOutputSurfaceHeight = 0; | ||||
|         mSurfaceCallback.onSurfaceDestroyed(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Called by the hosting {@link com.otaliastudios.cameraview.CameraView}, | ||||
|      * this is a lifecycle event. | ||||
|      */ | ||||
|     public void onResume() {} | ||||
| 
 | ||||
|     /** | ||||
|      * Called by the hosting {@link com.otaliastudios.cameraview.CameraView}, | ||||
|      * this is a lifecycle event. | ||||
|      */ | ||||
|     public void onPause() {} | ||||
| 
 | ||||
|     /** | ||||
|      * Called by the hosting {@link com.otaliastudios.cameraview.CameraView}, | ||||
|      * this is a lifecycle event. | ||||
|      */ | ||||
|     public void onDestroy() {} | ||||
| 
 | ||||
|     /** | ||||
|      * Here we must crop the visible part by applying a > 1 scale to one of our | ||||
|      * dimensions. This way our internal aspect ratio (mOutputSurfaceWidth / mOutputSurfaceHeight) | ||||
|      * will match the preview size aspect ratio (mInputStreamWidth / mInputStreamHeight). | ||||
|      * | ||||
|      * There might still be some absolute difference (e.g. same ratio but bigger / smaller). | ||||
|      * However that should be already managed by the framework. | ||||
|      */ | ||||
|     protected void crop(@NonNull Task<Void> task) { | ||||
|         // The base implementation does not support cropping.
 | ||||
|         task.start(); | ||||
|         task.end(null); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Whether this preview implementation supports cropping. | ||||
|      * The base implementation does not, but it is strongly recommended to do so. | ||||
|      * @return true if cropping is supported | ||||
|      */ | ||||
|     public boolean supportsCropping() { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Whether we are currently cropping the output. | ||||
|      * If false, this means that the output image will match the visible bounds. | ||||
|      * @return true if cropping | ||||
|      */ | ||||
|     public boolean isCropping() { | ||||
|         return mCropping; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,32 @@ | ||||
| package com.otaliastudios.cameraview.preview; | ||||
| 
 | ||||
| import android.graphics.SurfaceTexture; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| 
 | ||||
| /** | ||||
|  * Callback for renderer frames. | ||||
|  */ | ||||
| public interface RendererFrameCallback { | ||||
| 
 | ||||
|     /** | ||||
|      * Called on the renderer thread, hopefully only once, to notify that | ||||
|      * the texture was created (or to inform a new callback of the old texture). | ||||
|      * | ||||
|      * @param textureId the GL texture linked to the image stream | ||||
|      */ | ||||
|     @RendererThread | ||||
|     void onRendererTextureCreated(int textureId); | ||||
| 
 | ||||
|     /** | ||||
|      * Called on the renderer thread after each frame was drawn. | ||||
|      * You are not supposed to hold for too long onto this thread, because | ||||
|      * well, it is the rendering thread. | ||||
|      * | ||||
|      * @param surfaceTexture the texture to get transformation | ||||
|      * @param scaleX the scaleX (in REF_VIEW) value | ||||
|      * @param scaleY the scaleY (in REF_VIEW) value | ||||
|      */ | ||||
|     @RendererThread | ||||
|     void onRendererFrame(@NonNull SurfaceTexture surfaceTexture, float scaleX, float scaleY); | ||||
| } | ||||
| @ -0,0 +1,6 @@ | ||||
| package com.otaliastudios.cameraview.preview; | ||||
| 
 | ||||
| /** | ||||
|  * Indicates that some action is being executed on the renderer thread. | ||||
|  */ | ||||
| public @interface RendererThread {} | ||||
| @ -1,4 +1,4 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| package com.otaliastudios.cameraview.size; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| 
 | ||||
| @ -0,0 +1,92 @@ | ||||
| package com.otaliastudios.cameraview.size; | ||||
| 
 | ||||
| import android.content.res.TypedArray; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.R; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| 
 | ||||
| /** | ||||
|  * Parses size selectors from XML attributes. | ||||
|  */ | ||||
| public class SizeSelectorParser { | ||||
| 
 | ||||
|     private SizeSelector pictureSizeSelector; | ||||
|     private SizeSelector videoSizeSelector; | ||||
| 
 | ||||
|     public SizeSelectorParser(@NonNull TypedArray array) { | ||||
|         List<SizeSelector> pictureConstraints = new ArrayList<>(3); | ||||
|         if (array.hasValue(R.styleable.CameraView_cameraPictureSizeMinWidth)) { | ||||
|             pictureConstraints.add(SizeSelectors.minWidth(array.getInteger(R.styleable.CameraView_cameraPictureSizeMinWidth, 0))); | ||||
|         } | ||||
|         if (array.hasValue(R.styleable.CameraView_cameraPictureSizeMaxWidth)) { | ||||
|             pictureConstraints.add(SizeSelectors.maxWidth(array.getInteger(R.styleable.CameraView_cameraPictureSizeMaxWidth, 0))); | ||||
|         } | ||||
|         if (array.hasValue(R.styleable.CameraView_cameraPictureSizeMinHeight)) { | ||||
|             pictureConstraints.add(SizeSelectors.minHeight(array.getInteger(R.styleable.CameraView_cameraPictureSizeMinHeight, 0))); | ||||
|         } | ||||
|         if (array.hasValue(R.styleable.CameraView_cameraPictureSizeMaxHeight)) { | ||||
|             pictureConstraints.add(SizeSelectors.maxHeight(array.getInteger(R.styleable.CameraView_cameraPictureSizeMaxHeight, 0))); | ||||
|         } | ||||
|         if (array.hasValue(R.styleable.CameraView_cameraPictureSizeMinArea)) { | ||||
|             pictureConstraints.add(SizeSelectors.minArea(array.getInteger(R.styleable.CameraView_cameraPictureSizeMinArea, 0))); | ||||
|         } | ||||
|         if (array.hasValue(R.styleable.CameraView_cameraPictureSizeMaxArea)) { | ||||
|             pictureConstraints.add(SizeSelectors.maxArea(array.getInteger(R.styleable.CameraView_cameraPictureSizeMaxArea, 0))); | ||||
|         } | ||||
|         if (array.hasValue(R.styleable.CameraView_cameraPictureSizeAspectRatio)) { | ||||
|             //noinspection ConstantConditions
 | ||||
|             pictureConstraints.add(SizeSelectors.aspectRatio(AspectRatio.parse(array.getString(R.styleable.CameraView_cameraPictureSizeAspectRatio)), 0)); | ||||
|         } | ||||
| 
 | ||||
|         if (array.getBoolean(R.styleable.CameraView_cameraPictureSizeSmallest, false)) pictureConstraints.add(SizeSelectors.smallest()); | ||||
|         if (array.getBoolean(R.styleable.CameraView_cameraPictureSizeBiggest, false)) pictureConstraints.add(SizeSelectors.biggest()); | ||||
|         pictureSizeSelector = !pictureConstraints.isEmpty() ? | ||||
|                 SizeSelectors.and(pictureConstraints.toArray(new SizeSelector[0])) : | ||||
|                 SizeSelectors.biggest(); | ||||
| 
 | ||||
|         // Video size selector
 | ||||
|         List<SizeSelector> videoConstraints = new ArrayList<>(3); | ||||
|         if (array.hasValue(R.styleable.CameraView_cameraVideoSizeMinWidth)) { | ||||
|             videoConstraints.add(SizeSelectors.minWidth(array.getInteger(R.styleable.CameraView_cameraVideoSizeMinWidth, 0))); | ||||
|         } | ||||
|         if (array.hasValue(R.styleable.CameraView_cameraVideoSizeMaxWidth)) { | ||||
|             videoConstraints.add(SizeSelectors.maxWidth(array.getInteger(R.styleable.CameraView_cameraVideoSizeMaxWidth, 0))); | ||||
|         } | ||||
|         if (array.hasValue(R.styleable.CameraView_cameraVideoSizeMinHeight)) { | ||||
|             videoConstraints.add(SizeSelectors.minHeight(array.getInteger(R.styleable.CameraView_cameraVideoSizeMinHeight, 0))); | ||||
|         } | ||||
|         if (array.hasValue(R.styleable.CameraView_cameraVideoSizeMaxHeight)) { | ||||
|             videoConstraints.add(SizeSelectors.maxHeight(array.getInteger(R.styleable.CameraView_cameraVideoSizeMaxHeight, 0))); | ||||
|         } | ||||
|         if (array.hasValue(R.styleable.CameraView_cameraVideoSizeMinArea)) { | ||||
|             videoConstraints.add(SizeSelectors.minArea(array.getInteger(R.styleable.CameraView_cameraVideoSizeMinArea, 0))); | ||||
|         } | ||||
|         if (array.hasValue(R.styleable.CameraView_cameraVideoSizeMaxArea)) { | ||||
|             videoConstraints.add(SizeSelectors.maxArea(array.getInteger(R.styleable.CameraView_cameraVideoSizeMaxArea, 0))); | ||||
|         } | ||||
|         if (array.hasValue(R.styleable.CameraView_cameraVideoSizeAspectRatio)) { | ||||
|             //noinspection ConstantConditions
 | ||||
|             videoConstraints.add(SizeSelectors.aspectRatio(AspectRatio.parse(array.getString(R.styleable.CameraView_cameraVideoSizeAspectRatio)), 0)); | ||||
|         } | ||||
|         if (array.getBoolean(R.styleable.CameraView_cameraVideoSizeSmallest, false)) videoConstraints.add(SizeSelectors.smallest()); | ||||
|         if (array.getBoolean(R.styleable.CameraView_cameraVideoSizeBiggest, false)) videoConstraints.add(SizeSelectors.biggest()); | ||||
|         videoSizeSelector = !videoConstraints.isEmpty() ? | ||||
|                 SizeSelectors.and(videoConstraints.toArray(new SizeSelector[0])) : | ||||
|                 SizeSelectors.biggest(); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     public SizeSelector getPictureSizeSelector() { | ||||
|         return pictureSizeSelector; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     public SizeSelector getVideoSizeSelector() { | ||||
|         return videoSizeSelector; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| package com.otaliastudios.cameraview; | ||||
| package com.otaliastudios.cameraview.size; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| 
 | ||||
Some files were not shown because too many files have changed in this diff Show More
					Loading…
					
					
				
		Reference in new issue