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