Refactor code into subpackages

pull/482/head
Mattia Iavarone 6 years ago
parent 0c7726d5c5
commit 7603a0f11b
  1. 1
      cameraview/build.gradle
  2. 12
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraOptions1Test.java
  3. 3
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraPreviewTest.java
  4. 4
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewCallbacksTest.java
  5. 110
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java
  6. 3
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CropHelperTest.java
  7. 13
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/GestureLayoutTest.java
  8. 2
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/GridLinesLayoutTest.java
  9. 7
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/IntegrationTest.java
  10. 5
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/Mapper1Test.java
  11. 11
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraController.java
  12. 3
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/PictureResultTest.java
  13. 8
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/PinchGestureLayoutTest.java
  14. 10
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/ScrollGestureLayoutTest.java
  15. 8
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/TapGestureLayoutTest.java
  16. 2
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/TestActivity.java
  17. 5
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/VideoResultTest.java
  18. 6
      cameraview/src/main/gles/com/otaliastudios/cameraview/Pool.java
  19. 3
      cameraview/src/main/gles/com/otaliastudios/cameraview/TextureMediaEncoder.java
  20. 20
      cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java
  21. 16
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java
  22. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraException.java
  23. 14
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java
  24. 139
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  25. 5
      cameraview/src/main/java/com/otaliastudios/cameraview/Frame.java
  26. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/FrameManager.java
  27. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/FullVideoRecorder.java
  28. 9
      cameraview/src/main/java/com/otaliastudios/cameraview/Mapper.java
  29. 5
      cameraview/src/main/java/com/otaliastudios/cameraview/Mapper1.java
  30. 3
      cameraview/src/main/java/com/otaliastudios/cameraview/PictureResult.java
  31. 3
      cameraview/src/main/java/com/otaliastudios/cameraview/SnapshotPictureRecorder.java
  32. 3
      cameraview/src/main/java/com/otaliastudios/cameraview/SnapshotVideoRecorder.java
  33. 6
      cameraview/src/main/java/com/otaliastudios/cameraview/VideoResult.java
  34. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/controls/Audio.java
  35. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/controls/Control.java
  36. 72
      cameraview/src/main/java/com/otaliastudios/cameraview/controls/ControlParser.java
  37. 6
      cameraview/src/main/java/com/otaliastudios/cameraview/controls/Facing.java
  38. 5
      cameraview/src/main/java/com/otaliastudios/cameraview/controls/Flash.java
  39. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/controls/Grid.java
  40. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/controls/Hdr.java
  41. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/controls/Mode.java
  42. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/controls/Preview.java
  43. 5
      cameraview/src/main/java/com/otaliastudios/cameraview/controls/VideoCodec.java
  44. 5
      cameraview/src/main/java/com/otaliastudios/cameraview/controls/WhiteBalance.java
  45. 40
      cameraview/src/main/java/com/otaliastudios/cameraview/gesture/Gesture.java
  46. 36
      cameraview/src/main/java/com/otaliastudios/cameraview/gesture/GestureAction.java
  47. 159
      cameraview/src/main/java/com/otaliastudios/cameraview/gesture/GestureLayout.java
  48. 53
      cameraview/src/main/java/com/otaliastudios/cameraview/gesture/GestureParser.java
  49. 31
      cameraview/src/main/java/com/otaliastudios/cameraview/gesture/GestureType.java
  50. 46
      cameraview/src/main/java/com/otaliastudios/cameraview/gesture/PinchGestureLayout.java
  51. 46
      cameraview/src/main/java/com/otaliastudios/cameraview/gesture/ScrollGestureLayout.java
  52. 33
      cameraview/src/main/java/com/otaliastudios/cameraview/gesture/TapGestureLayout.java
  53. 10
      cameraview/src/main/java/com/otaliastudios/cameraview/size/AspectRatio.java
  54. 12
      cameraview/src/main/java/com/otaliastudios/cameraview/size/Size.java
  55. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/size/SizeSelector.java
  56. 102
      cameraview/src/main/java/com/otaliastudios/cameraview/size/SizeSelectorParser.java
  57. 6
      cameraview/src/main/java/com/otaliastudios/cameraview/size/SizeSelectors.java
  58. 29
      cameraview/src/main/utils/com/otaliastudios/cameraview/CameraLogger.java
  59. 4
      cameraview/src/main/utils/com/otaliastudios/cameraview/CameraUtils.java
  60. 4
      cameraview/src/main/utils/com/otaliastudios/cameraview/CropHelper.java
  61. 2
      cameraview/src/main/utils/com/otaliastudios/cameraview/RotationHelper.java
  62. 2
      cameraview/src/main/views/com/otaliastudios/cameraview/CameraPreview.java
  63. 70
      cameraview/src/main/views/com/otaliastudios/cameraview/GestureLayout.java
  64. 2
      cameraview/src/main/views/com/otaliastudios/cameraview/GlCameraPreview.java
  65. 2
      cameraview/src/main/views/com/otaliastudios/cameraview/GridLinesLayout.java
  66. 2
      cameraview/src/main/views/com/otaliastudios/cameraview/TextureCameraPreview.java
  67. 5
      cameraview/src/test/java/com/otaliastudios/cameraview/AspectRatioTest.java
  68. 6
      cameraview/src/test/java/com/otaliastudios/cameraview/FrameManagerTest.java
  69. 3
      cameraview/src/test/java/com/otaliastudios/cameraview/FrameTest.java
  70. 5
      cameraview/src/test/java/com/otaliastudios/cameraview/SizeSelectorsTest.java
  71. 2
      cameraview/src/test/java/com/otaliastudios/cameraview/SizeTest.java
  72. 6
      demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java
  73. 20
      demo/src/main/java/com/otaliastudios/cameraview/demo/Control.java
  74. 2
      demo/src/main/java/com/otaliastudios/cameraview/demo/PicturePreviewActivity.java

@ -32,7 +32,6 @@ android {
} }
sourceSets { sourceSets {
main.java.srcDirs += 'src/main/options'
main.java.srcDirs += 'src/main/views' main.java.srcDirs += 'src/main/views'
main.java.srcDirs += 'src/main/utils' main.java.srcDirs += 'src/main/utils'
main.java.srcDirs += 'src/main/gles' main.java.srcDirs += 'src/main/gles'

@ -3,6 +3,18 @@ package com.otaliastudios.cameraview;
import android.hardware.Camera; import android.hardware.Camera;
import com.otaliastudios.cameraview.controls.Audio;
import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.controls.Flash;
import com.otaliastudios.cameraview.gesture.GestureAction;
import com.otaliastudios.cameraview.controls.Grid;
import com.otaliastudios.cameraview.controls.Hdr;
import com.otaliastudios.cameraview.controls.Mode;
import com.otaliastudios.cameraview.controls.VideoCodec;
import com.otaliastudios.cameraview.controls.WhiteBalance;
import com.otaliastudios.cameraview.size.AspectRatio;
import com.otaliastudios.cameraview.size.Size;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;

@ -5,6 +5,9 @@ import android.content.Context;
import androidx.test.rule.ActivityTestRule; import androidx.test.rule.ActivityTestRule;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.otaliastudios.cameraview.size.AspectRatio;
import com.otaliastudios.cameraview.size.Size;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;

@ -9,6 +9,10 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest; import androidx.test.filters.MediumTest;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.otaliastudios.cameraview.controls.Audio;
import com.otaliastudios.cameraview.gesture.Gesture;
import com.otaliastudios.cameraview.gesture.GestureAction;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;

@ -2,6 +2,7 @@ package com.otaliastudios.cameraview;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray;
import android.location.Location; import android.location.Location;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
@ -9,6 +10,25 @@ import androidx.test.filters.MediumTest;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.otaliastudios.cameraview.controls.Audio;
import com.otaliastudios.cameraview.controls.ControlParser;
import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.controls.Flash;
import com.otaliastudios.cameraview.gesture.Gesture;
import com.otaliastudios.cameraview.gesture.GestureAction;
import com.otaliastudios.cameraview.controls.Grid;
import com.otaliastudios.cameraview.controls.Hdr;
import com.otaliastudios.cameraview.controls.Mode;
import com.otaliastudios.cameraview.controls.VideoCodec;
import com.otaliastudios.cameraview.controls.WhiteBalance;
import com.otaliastudios.cameraview.gesture.GestureParser;
import com.otaliastudios.cameraview.gesture.PinchGestureLayout;
import com.otaliastudios.cameraview.gesture.ScrollGestureLayout;
import com.otaliastudios.cameraview.gesture.TapGestureLayout;
import com.otaliastudios.cameraview.size.Size;
import com.otaliastudios.cameraview.size.SizeSelector;
import com.otaliastudios.cameraview.size.SizeSelectors;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -106,14 +126,16 @@ public class CameraViewTest extends BaseTest {
@Test @Test
public void testDefaults() { public void testDefaults() {
// CameraController // CameraController
assertEquals(cameraView.getFlash(), Flash.DEFAULT); TypedArray empty = context().obtainStyledAttributes(new int[]{});
assertEquals(cameraView.getFacing(), Facing.DEFAULT(context())); ControlParser controls = new ControlParser(context(), empty);
assertEquals(cameraView.getGrid(), Grid.DEFAULT); assertEquals(cameraView.getFlash(), controls.getFlash());
assertEquals(cameraView.getWhiteBalance(), WhiteBalance.DEFAULT); assertEquals(cameraView.getFacing(), controls.getFacing());
assertEquals(cameraView.getMode(), Mode.DEFAULT); assertEquals(cameraView.getGrid(), controls.getGrid());
assertEquals(cameraView.getHdr(), Hdr.DEFAULT); assertEquals(cameraView.getWhiteBalance(), controls.getWhiteBalance());
assertEquals(cameraView.getAudio(), Audio.DEFAULT); assertEquals(cameraView.getMode(), controls.getMode());
assertEquals(cameraView.getVideoCodec(), VideoCodec.DEFAULT); assertEquals(cameraView.getHdr(), controls.getHdr());
assertEquals(cameraView.getAudio(), controls.getAudio());
assertEquals(cameraView.getVideoCodec(), controls.getVideoCodec());
assertEquals(cameraView.getLocation(), null); assertEquals(cameraView.getLocation(), null);
assertEquals(cameraView.getExposureCorrection(), 0f, 0f); assertEquals(cameraView.getExposureCorrection(), 0f, 0f);
assertEquals(cameraView.getZoom(), 0f, 0f); assertEquals(cameraView.getZoom(), 0f, 0f);
@ -121,12 +143,13 @@ public class CameraViewTest extends BaseTest {
assertEquals(cameraView.getVideoMaxSize(), 0, 0); assertEquals(cameraView.getVideoMaxSize(), 0, 0);
// Self managed // Self managed
GestureParser gestures = new GestureParser(empty);
assertEquals(cameraView.getPlaySounds(), CameraView.DEFAULT_PLAY_SOUNDS); assertEquals(cameraView.getPlaySounds(), CameraView.DEFAULT_PLAY_SOUNDS);
assertEquals(cameraView.getGestureAction(Gesture.TAP), GestureAction.DEFAULT_TAP); assertEquals(cameraView.getGestureAction(Gesture.TAP), gestures.getTapAction());
assertEquals(cameraView.getGestureAction(Gesture.LONG_TAP), GestureAction.DEFAULT_LONG_TAP); assertEquals(cameraView.getGestureAction(Gesture.LONG_TAP), gestures.getLongTapAction());
assertEquals(cameraView.getGestureAction(Gesture.PINCH), GestureAction.DEFAULT_PINCH); assertEquals(cameraView.getGestureAction(Gesture.PINCH), gestures.getPinchAction());
assertEquals(cameraView.getGestureAction(Gesture.SCROLL_HORIZONTAL), GestureAction.DEFAULT_SCROLL_HORIZONTAL); assertEquals(cameraView.getGestureAction(Gesture.SCROLL_HORIZONTAL), gestures.getHorizontalScrollAction());
assertEquals(cameraView.getGestureAction(Gesture.SCROLL_VERTICAL), GestureAction.DEFAULT_SCROLL_VERTICAL); assertEquals(cameraView.getGestureAction(Gesture.SCROLL_VERTICAL), gestures.getVerticalScrollAction());
} }
//endregion //endregion
@ -159,21 +182,21 @@ public class CameraViewTest extends BaseTest {
// PinchGestureLayout // PinchGestureLayout
cameraView.mapGesture(Gesture.PINCH, GestureAction.ZOOM); cameraView.mapGesture(Gesture.PINCH, GestureAction.ZOOM);
assertTrue(cameraView.mPinchGestureLayout.enabled()); assertTrue(cameraView.mPinchGestureLayout.isActive());
cameraView.clearGesture(Gesture.PINCH); cameraView.clearGesture(Gesture.PINCH);
assertFalse(cameraView.mPinchGestureLayout.enabled()); assertFalse(cameraView.mPinchGestureLayout.isActive());
// TapGestureLayout // TapGestureLayout
cameraView.mapGesture(Gesture.TAP, GestureAction.CAPTURE); cameraView.mapGesture(Gesture.TAP, GestureAction.CAPTURE);
assertTrue(cameraView.mTapGestureLayout.enabled()); assertTrue(cameraView.mTapGestureLayout.isActive());
cameraView.clearGesture(Gesture.TAP); cameraView.clearGesture(Gesture.TAP);
assertFalse(cameraView.mPinchGestureLayout.enabled()); assertFalse(cameraView.mPinchGestureLayout.isActive());
// ScrollGestureLayout // ScrollGestureLayout
cameraView.mapGesture(Gesture.SCROLL_HORIZONTAL, GestureAction.ZOOM); cameraView.mapGesture(Gesture.SCROLL_HORIZONTAL, GestureAction.ZOOM);
assertTrue(cameraView.mScrollGestureLayout.enabled()); assertTrue(cameraView.mScrollGestureLayout.isActive());
cameraView.clearGesture(Gesture.SCROLL_HORIZONTAL); cameraView.clearGesture(Gesture.SCROLL_HORIZONTAL);
assertFalse(cameraView.mScrollGestureLayout.enabled()); assertFalse(cameraView.mScrollGestureLayout.isActive());
} }
//endregion //endregion
@ -188,10 +211,11 @@ public class CameraViewTest extends BaseTest {
@Override @Override
public void run() { public void run() {
cameraView.mTapGestureLayout = new TapGestureLayout(cameraView.getContext()) { cameraView.mTapGestureLayout = new TapGestureLayout(cameraView.getContext()) {
public boolean onTouchEvent(MotionEvent event) { return true; } protected boolean handleTouchEvent(@NonNull MotionEvent event) {
setGesture(Gesture.TAP);
return true;
}
}; };
cameraView.mTapGestureLayout.setGestureType(Gesture.TAP);
} }
}); });
cameraView.mapGesture(Gesture.TAP, GestureAction.CAPTURE); cameraView.mapGesture(Gesture.TAP, GestureAction.CAPTURE);
@ -207,9 +231,11 @@ public class CameraViewTest extends BaseTest {
@Override @Override
public void run() { public void run() {
cameraView.mTapGestureLayout = new TapGestureLayout(cameraView.getContext()) { cameraView.mTapGestureLayout = new TapGestureLayout(cameraView.getContext()) {
public boolean onTouchEvent(MotionEvent event) { return true; } protected boolean handleTouchEvent(@NonNull MotionEvent event) {
setGesture(Gesture.TAP);
return true;
}
}; };
cameraView.mTapGestureLayout.setGestureType(Gesture.TAP);
} }
}); });
mockController.mFocusStarted = false; mockController.mFocusStarted = false;
@ -224,30 +250,41 @@ public class CameraViewTest extends BaseTest {
assertTrue(mockController.mFocusStarted); assertTrue(mockController.mFocusStarted);
} }
private class FactorHolder { float value; }
@Test @Test
public void testGestureAction_zoom() { public void testGestureAction_zoom() {
mockController.mockStarted(true); mockController.mockStarted(true);
mockController.mZoomChanged = false; mockController.mZoomChanged = false;
MotionEvent event = MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0); MotionEvent event = MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0);
final FactorHolder factor = new FactorHolder();
ui(new Runnable() { ui(new Runnable() {
@Override @Override
public void run() { public void run() {
cameraView.mPinchGestureLayout = new PinchGestureLayout(cameraView.getContext()) { cameraView.mPinchGestureLayout = new PinchGestureLayout(cameraView.getContext()) {
public boolean onTouchEvent(MotionEvent event) { return true; } @Override
protected boolean handleTouchEvent(@NonNull MotionEvent event) {
setGesture(Gesture.PINCH);
return true;
}
@Override
protected float getFactor() {
return factor.value;
}
}; };
cameraView.mPinchGestureLayout.setGestureType(Gesture.PINCH);
cameraView.mapGesture(Gesture.PINCH, GestureAction.ZOOM); cameraView.mapGesture(Gesture.PINCH, GestureAction.ZOOM);
} }
}); });
// If factor is 0, we return the same value. The controller should not be notified. // If factor is 0, we return the same value. The controller should not be notified.
cameraView.mPinchGestureLayout.mFactor = 0f; factor.value = 0f;
cameraView.dispatchTouchEvent(event); cameraView.dispatchTouchEvent(event);
assertFalse(mockController.mZoomChanged); assertFalse(mockController.mZoomChanged);
// For larger factors, the value is scaled. The controller should be notified. // For larger factors, the value is scaled. The controller should be notified.
cameraView.mPinchGestureLayout.mFactor = 1f; factor.value = 1f;
cameraView.dispatchTouchEvent(event); cameraView.dispatchTouchEvent(event);
assertTrue(mockController.mZoomChanged); assertTrue(mockController.mZoomChanged);
} }
@ -262,24 +299,33 @@ public class CameraViewTest extends BaseTest {
mockController.mockStarted(true); mockController.mockStarted(true);
mockController.mExposureCorrectionChanged = false; mockController.mExposureCorrectionChanged = false;
MotionEvent event = MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0); MotionEvent event = MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0);
final FactorHolder factor = new FactorHolder();
ui(new Runnable() { ui(new Runnable() {
@Override @Override
public void run() { public void run() {
cameraView.mScrollGestureLayout = new ScrollGestureLayout(cameraView.getContext()) { cameraView.mScrollGestureLayout = new ScrollGestureLayout(cameraView.getContext()) {
public boolean onTouchEvent(MotionEvent event) { return true; } @Override
protected boolean handleTouchEvent(@NonNull MotionEvent event) {
setGesture(Gesture.SCROLL_HORIZONTAL);
return true;
}
@Override
protected float getFactor() {
return factor.value;
}
}; };
cameraView.mScrollGestureLayout.setGestureType(Gesture.SCROLL_HORIZONTAL);
cameraView.mapGesture(Gesture.SCROLL_HORIZONTAL, GestureAction.EXPOSURE_CORRECTION); cameraView.mapGesture(Gesture.SCROLL_HORIZONTAL, GestureAction.EXPOSURE_CORRECTION);
} }
}); });
// If factor is 0, we return the same value. The controller should not be notified. // If factor is 0, we return the same value. The controller should not be notified.
cameraView.mScrollGestureLayout.mFactor = 0f; factor.value = 0f;
cameraView.dispatchTouchEvent(event); cameraView.dispatchTouchEvent(event);
assertFalse(mockController.mExposureCorrectionChanged); assertFalse(mockController.mExposureCorrectionChanged);
// For larger factors, the value is scaled. The controller should be notified. // For larger factors, the value is scaled. The controller should be notified.
cameraView.mScrollGestureLayout.mFactor = 1f; factor.value = 1f;
cameraView.dispatchTouchEvent(event); cameraView.dispatchTouchEvent(event);
assertTrue(mockController.mExposureCorrectionChanged); assertTrue(mockController.mExposureCorrectionChanged);
} }

@ -3,6 +3,9 @@ package com.otaliastudios.cameraview;
import android.graphics.Rect; import android.graphics.Rect;
import com.otaliastudios.cameraview.size.AspectRatio;
import com.otaliastudios.cameraview.size.Size;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;

@ -3,17 +3,16 @@ package com.otaliastudios.cameraview;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import androidx.test.espresso.Espresso;
import androidx.test.espresso.Root;
import androidx.test.espresso.ViewAssertion;
import androidx.test.espresso.ViewInteraction; import androidx.test.espresso.ViewInteraction;
import androidx.test.espresso.assertion.ViewAssertions;
import androidx.test.espresso.matcher.RootMatchers; import androidx.test.espresso.matcher.RootMatchers;
import androidx.test.espresso.matcher.ViewMatchers;
import androidx.test.rule.ActivityTestRule; import androidx.test.rule.ActivityTestRule;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import com.otaliastudios.cameraview.gesture.Gesture;
import com.otaliastudios.cameraview.gesture.GestureLayout;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
@ -40,7 +39,7 @@ public abstract class GestureLayoutTest<T extends GestureLayout> extends BaseTes
public void run() { public void run() {
TestActivity a = rule.getActivity(); TestActivity a = rule.getActivity();
layout = create(a); layout = create(a);
layout.enable(true); layout.setActive(true);
a.inflate(layout); a.inflate(layout);
touch = new Task<>(); touch = new Task<>();
@ -48,7 +47,7 @@ public abstract class GestureLayoutTest<T extends GestureLayout> extends BaseTes
@Override @Override
public boolean onTouch(View view, MotionEvent motionEvent) { public boolean onTouch(View view, MotionEvent motionEvent) {
boolean found = layout.onTouchEvent(motionEvent); boolean found = layout.onTouchEvent(motionEvent);
if (found) touch.end(layout.getGestureType()); if (found) touch.end(layout.getGesture());
return true; return true;
} }
}); });

@ -1,6 +1,8 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview;
import com.otaliastudios.cameraview.controls.Grid;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest; import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule; import androidx.test.rule.ActivityTestRule;

@ -6,6 +6,13 @@ import android.graphics.PointF;
import android.hardware.Camera; import android.hardware.Camera;
import android.os.Build; import android.os.Build;
import com.otaliastudios.cameraview.controls.Audio;
import com.otaliastudios.cameraview.controls.Flash;
import com.otaliastudios.cameraview.controls.Hdr;
import com.otaliastudios.cameraview.controls.Mode;
import com.otaliastudios.cameraview.controls.WhiteBalance;
import com.otaliastudios.cameraview.size.Size;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest; import androidx.test.filters.MediumTest;

@ -3,6 +3,11 @@ package com.otaliastudios.cameraview;
import android.hardware.Camera; import android.hardware.Camera;
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 androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;

@ -3,6 +3,17 @@ package com.otaliastudios.cameraview;
import android.graphics.PointF; import android.graphics.PointF;
import android.location.Location; import android.location.Location;
import com.otaliastudios.cameraview.controls.Audio;
import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.controls.Flash;
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 androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;

@ -3,6 +3,9 @@ package com.otaliastudios.cameraview;
import android.location.Location; import android.location.Location;
import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.size.Size;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;

@ -2,6 +2,10 @@ package com.otaliastudios.cameraview;
import android.content.Context; import android.content.Context;
import com.otaliastudios.cameraview.gesture.Gesture;
import com.otaliastudios.cameraview.gesture.PinchGestureLayout;
import androidx.test.espresso.ViewAction; import androidx.test.espresso.ViewAction;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
@ -26,7 +30,7 @@ public class PinchGestureLayoutTest extends GestureLayoutTest<PinchGestureLayout
@Test @Test
public void testDefaults() { public void testDefaults() {
assertEquals(layout.getGestureType(), Gesture.PINCH); assertEquals(layout.getGesture(), Gesture.PINCH);
assertEquals(layout.getPoints().length, 2); assertEquals(layout.getPoints().length, 2);
assertEquals(layout.getPoints()[0].x, 0, 0); assertEquals(layout.getPoints()[0].x, 0, 0);
assertEquals(layout.getPoints()[0].y, 0, 0); assertEquals(layout.getPoints()[0].y, 0, 0);
@ -54,7 +58,7 @@ public class PinchGestureLayoutTest extends GestureLayoutTest<PinchGestureLayout
// How will this move our parameter? // How will this move our parameter?
float curr = 0.5f, min = 0f, max = 1f; float curr = 0.5f, min = 0f, max = 1f;
float newValue = layout.scaleValue(curr, min, max); float newValue = layout.getValue(curr, min, max);
if (increasing) { if (increasing) {
assertTrue(newValue > curr); assertTrue(newValue > curr);
assertTrue(newValue <= max); assertTrue(newValue <= max);

@ -2,6 +2,10 @@ package com.otaliastudios.cameraview;
import android.content.Context; import android.content.Context;
import com.otaliastudios.cameraview.gesture.Gesture;
import com.otaliastudios.cameraview.gesture.ScrollGestureLayout;
import androidx.test.espresso.ViewAction; import androidx.test.espresso.ViewAction;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
@ -31,7 +35,7 @@ public class ScrollGestureLayoutTest extends GestureLayoutTest<ScrollGestureLayo
@Test @Test
public void testDefaults() { public void testDefaults() {
assertNull(layout.getGestureType()); assertNull(layout.getGesture());
assertEquals(layout.getPoints().length, 2); assertEquals(layout.getPoints().length, 2);
assertEquals(layout.getPoints()[0].x, 0, 0); assertEquals(layout.getPoints()[0].x, 0, 0);
assertEquals(layout.getPoints()[0].y, 0, 0); assertEquals(layout.getPoints()[0].y, 0, 0);
@ -41,7 +45,7 @@ public class ScrollGestureLayoutTest extends GestureLayoutTest<ScrollGestureLayo
@Test @Test
public void testScrollDisabled() { public void testScrollDisabled() {
layout.enable(false); layout.setActive(false);
touch.listen(); touch.listen();
touch.start(); touch.start();
onLayout().perform(swipeUp()); onLayout().perform(swipeUp());
@ -58,7 +62,7 @@ public class ScrollGestureLayoutTest extends GestureLayoutTest<ScrollGestureLayo
// How will this move our parameter? // How will this move our parameter?
float curr = 0.5f, min = 0f, max = 1f; float curr = 0.5f, min = 0f, max = 1f;
float newValue = layout.scaleValue(curr, min, max); float newValue = layout.getValue(curr, min, max);
if (increasing) { if (increasing) {
assertTrue(newValue > curr); assertTrue(newValue > curr);
assertTrue(newValue <= max); assertTrue(newValue <= max);

@ -11,6 +11,10 @@ import androidx.test.filters.SmallTest;
import android.view.InputDevice; import android.view.InputDevice;
import android.view.MotionEvent; import android.view.MotionEvent;
import com.otaliastudios.cameraview.gesture.Gesture;
import com.otaliastudios.cameraview.gesture.TapGestureLayout;
import com.otaliastudios.cameraview.size.Size;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -28,7 +32,7 @@ public class TapGestureLayoutTest extends GestureLayoutTest<TapGestureLayout> {
@Test @Test
public void testDefaults() { public void testDefaults() {
assertNull(layout.getGestureType()); assertNull(layout.getGesture());
assertEquals(layout.getPoints().length, 1); assertEquals(layout.getPoints().length, 1);
assertEquals(layout.getPoints()[0].x, 0, 0); assertEquals(layout.getPoints()[0].x, 0, 0);
assertEquals(layout.getPoints()[0].y, 0, 0); assertEquals(layout.getPoints()[0].y, 0, 0);
@ -52,7 +56,7 @@ public class TapGestureLayoutTest extends GestureLayoutTest<TapGestureLayout> {
@Test @Test
public void testTapWhileDisabled() { public void testTapWhileDisabled() {
layout.enable(false); layout.setActive(false);
touch.listen(); touch.listen();
touch.start(); touch.start();
onLayout().perform(click()); onLayout().perform(click());

@ -12,6 +12,8 @@ import android.view.ViewGroup;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import com.otaliastudios.cameraview.size.Size;
import static android.view.ViewGroup.LayoutParams.*; import static android.view.ViewGroup.LayoutParams.*;
public class TestActivity extends Activity { public class TestActivity extends Activity {

@ -3,6 +3,11 @@ package com.otaliastudios.cameraview;
import android.location.Location; import android.location.Location;
import com.otaliastudios.cameraview.controls.Audio;
import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.controls.VideoCodec;
import com.otaliastudios.cameraview.size.Size;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;

@ -26,8 +26,8 @@ class Pool<T> {
this.factory = factory; this.factory = factory;
} }
boolean canGet() { boolean isEmpty() {
return count() < maxPoolSize; return count() >= maxPoolSize;
} }
@Nullable @Nullable
@ -39,7 +39,7 @@ class Pool<T> {
return buffer; return buffer;
} }
if (!canGet()) { if (isEmpty()) {
LOG.v("GET: Returning null. Too much items requested.", this); LOG.v("GET: Returning null. Too much items requested.", this);
return null; return null;
} }

@ -3,7 +3,6 @@ package com.otaliastudios.cameraview;
import android.opengl.EGLContext; import android.opengl.EGLContext;
import android.opengl.Matrix; import android.opengl.Matrix;
import android.os.Build; import android.os.Build;
import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -63,7 +62,7 @@ class TextureMediaEncoder extends VideoMediaEncoder<TextureMediaEncoder.Config>
@NonNull @NonNull
TextureFrame acquireFrame() { TextureFrame acquireFrame() {
if (!mFramePool.canGet()) { if (mFramePool.isEmpty()) {
throw new RuntimeException("Need more frames than this! Please increase the pool size."); throw new RuntimeException("Need more frames than this! Please increase the pool size.");
} else { } else {
//noinspection ConstantConditions //noinspection ConstantConditions

@ -14,6 +14,16 @@ import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread; import androidx.annotation.WorkerThread;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
import com.otaliastudios.cameraview.controls.Audio;
import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.controls.Flash;
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 java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -236,10 +246,10 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
Camera.Parameters params = mCamera.getParameters(); Camera.Parameters params = mCamera.getParameters();
mCameraOptions = new CameraOptions(params, flip(REF_SENSOR, REF_VIEW)); mCameraOptions = new CameraOptions(params, flip(REF_SENSOR, REF_VIEW));
applyDefaultFocus(params); applyDefaultFocus(params);
applyFlash(params, Flash.DEFAULT); applyFlash(params, Flash.OFF);
applyLocation(params, null); applyLocation(params, null);
applyWhiteBalance(params, WhiteBalance.DEFAULT); applyWhiteBalance(params, WhiteBalance.AUTO);
applyHdr(params, Hdr.DEFAULT); applyHdr(params, Hdr.OFF);
applyPlaySounds(mPlaySounds); applyPlaySounds(mPlaySounds);
params.setRecordingHint(mMode == Mode.VIDEO); params.setRecordingHint(mMode == Mode.VIDEO);
mCamera.setParameters(params); mCamera.setParameters(params);
@ -596,7 +606,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
result.facing = mFacing; result.facing = mFacing;
result.size = getUncroppedSnapshotSize(REF_OUTPUT); // Not the real size: it will be cropped to match the view ratio result.size = getUncroppedSnapshotSize(REF_OUTPUT); // Not the real size: it will be cropped to match the view ratio
result.rotation = offset(REF_SENSOR, REF_OUTPUT); // Actually it will be rotated and set to 0. result.rotation = offset(REF_SENSOR, REF_OUTPUT); // Actually it will be rotated and set to 0.
AspectRatio outputRatio = flip(REF_OUTPUT, REF_VIEW) ? viewAspectRatio.inverse() : viewAspectRatio; AspectRatio outputRatio = flip(REF_OUTPUT, REF_VIEW) ? viewAspectRatio.flip() : viewAspectRatio;
// LOG.e("ROTBUG_pic", "aspectRatio (REF_VIEW):", viewAspectRatio); // LOG.e("ROTBUG_pic", "aspectRatio (REF_VIEW):", viewAspectRatio);
// LOG.e("ROTBUG_pic", "aspectRatio (REF_OUTPUT):", outputRatio); // LOG.e("ROTBUG_pic", "aspectRatio (REF_OUTPUT):", outputRatio);
// LOG.e("ROTBUG_pic", "sizeUncropped (REF_OUTPUT):", result.size); // LOG.e("ROTBUG_pic", "sizeUncropped (REF_OUTPUT):", result.size);
@ -766,7 +776,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
// and maybe we can improve. The reason why this happen is beyond my understanding. // and maybe we can improve. The reason why this happen is beyond my understanding.
Size outputSize = getUncroppedSnapshotSize(REF_OUTPUT); Size outputSize = getUncroppedSnapshotSize(REF_OUTPUT);
AspectRatio outputRatio = flip(REF_OUTPUT, REF_VIEW) ? viewAspectRatio.inverse() : viewAspectRatio; AspectRatio outputRatio = flip(REF_OUTPUT, REF_VIEW) ? viewAspectRatio.flip() : viewAspectRatio;
Rect outputCrop = CropHelper.computeCrop(outputSize, outputRatio); Rect outputCrop = CropHelper.computeCrop(outputSize, outputRatio);
outputSize = new Size(outputCrop.width(), outputCrop.height()); outputSize = new Size(outputCrop.width(), outputCrop.height());
videoResult.size = outputSize; videoResult.size = outputSize;

@ -6,6 +6,20 @@ import android.location.Location;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import com.otaliastudios.cameraview.controls.Audio;
import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.controls.Flash;
import com.otaliastudios.cameraview.gesture.Gesture;
import com.otaliastudios.cameraview.controls.Hdr;
import com.otaliastudios.cameraview.controls.Mode;
import com.otaliastudios.cameraview.controls.VideoCodec;
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 com.otaliastudios.cameraview.size.SizeSelectors;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
@ -638,7 +652,7 @@ abstract class CameraController implements
// is null, or if it fails in finding a size. // is null, or if it fails in finding a size.
Size targetMinSize = getPreviewSurfaceSize(REF_VIEW); Size targetMinSize = getPreviewSurfaceSize(REF_VIEW);
AspectRatio targetRatio = AspectRatio.of(mCaptureSize.getWidth(), mCaptureSize.getHeight()); AspectRatio targetRatio = AspectRatio.of(mCaptureSize.getWidth(), mCaptureSize.getHeight());
if (flip) targetRatio = targetRatio.inverse(); if (flip) targetRatio = targetRatio.flip();
LOG.i("size:", "computePreviewStreamSize:", "targetRatio:", targetRatio, "targetMinSize:", targetMinSize); LOG.i("size:", "computePreviewStreamSize:", "targetRatio:", targetRatio, "targetMinSize:", targetMinSize);
SizeSelector matchRatio = SizeSelectors.and( // Match this aspect ratio and sort by biggest SizeSelector matchRatio = SizeSelectors.and( // Match this aspect ratio and sort by biggest
SizeSelectors.aspectRatio(targetRatio, 0), SizeSelectors.aspectRatio(targetRatio, 0),

@ -1,6 +1,8 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview;
import com.otaliastudios.cameraview.controls.Facing;
/** /**
* Holds an error with the camera configuration. * Holds an error with the camera configuration.
*/ */

@ -4,6 +4,20 @@ package com.otaliastudios.cameraview;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.hardware.Camera; import android.hardware.Camera;
import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraCharacteristics;
import com.otaliastudios.cameraview.controls.Audio;
import com.otaliastudios.cameraview.controls.Control;
import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.controls.Flash;
import com.otaliastudios.cameraview.gesture.GestureAction;
import com.otaliastudios.cameraview.controls.Grid;
import com.otaliastudios.cameraview.controls.Hdr;
import com.otaliastudios.cameraview.controls.Mode;
import com.otaliastudios.cameraview.controls.VideoCodec;
import com.otaliastudios.cameraview.controls.WhiteBalance;
import com.otaliastudios.cameraview.size.AspectRatio;
import com.otaliastudios.cameraview.size.Size;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.util.Arrays; import java.util.Arrays;

@ -28,6 +28,30 @@ import android.view.MotionEvent;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import com.otaliastudios.cameraview.controls.Audio;
import com.otaliastudios.cameraview.controls.Control;
import com.otaliastudios.cameraview.controls.ControlParser;
import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.controls.Flash;
import com.otaliastudios.cameraview.gesture.Gesture;
import com.otaliastudios.cameraview.gesture.GestureAction;
import com.otaliastudios.cameraview.controls.Grid;
import com.otaliastudios.cameraview.controls.Hdr;
import com.otaliastudios.cameraview.controls.Mode;
import com.otaliastudios.cameraview.controls.Preview;
import com.otaliastudios.cameraview.controls.VideoCodec;
import com.otaliastudios.cameraview.controls.WhiteBalance;
import com.otaliastudios.cameraview.gesture.GestureLayout;
import com.otaliastudios.cameraview.gesture.GestureParser;
import com.otaliastudios.cameraview.gesture.PinchGestureLayout;
import com.otaliastudios.cameraview.gesture.ScrollGestureLayout;
import com.otaliastudios.cameraview.gesture.TapGestureLayout;
import com.otaliastudios.cameraview.size.AspectRatio;
import com.otaliastudios.cameraview.size.Size;
import com.otaliastudios.cameraview.size.SizeSelector;
import com.otaliastudios.cameraview.size.SizeSelectorParser;
import com.otaliastudios.cameraview.size.SizeSelectors;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@ -94,11 +118,12 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
private void init(@NonNull Context context, @Nullable AttributeSet attrs) { private void init(@NonNull Context context, @Nullable AttributeSet attrs) {
setWillNotDraw(false); setWillNotDraw(false);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CameraView, 0, 0); TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CameraView, 0, 0);
ControlParser controls = new ControlParser(context, a);
// Self managed // Self managed
boolean playSounds = a.getBoolean(R.styleable.CameraView_cameraPlaySounds, DEFAULT_PLAY_SOUNDS); boolean playSounds = a.getBoolean(R.styleable.CameraView_cameraPlaySounds, DEFAULT_PLAY_SOUNDS);
mExperimental = a.getBoolean(R.styleable.CameraView_cameraExperimental, false); mExperimental = a.getBoolean(R.styleable.CameraView_cameraExperimental, false);
mPreview = Preview.fromValue(a.getInteger(R.styleable.CameraView_cameraPreview, Preview.DEFAULT.value())); mPreview = controls.getPreview();
// Camera controller params // Camera controller params
Facing facing = Facing.fromValue(a.getInteger(R.styleable.CameraView_cameraFacing, Facing.DEFAULT(context).value())); Facing facing = Facing.fromValue(a.getInteger(R.styleable.CameraView_cameraFacing, Facing.DEFAULT(context).value()));
@ -116,73 +141,9 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
int audioBitRate = a.getInteger(R.styleable.CameraView_cameraAudioBitRate, 0); int audioBitRate = a.getInteger(R.styleable.CameraView_cameraAudioBitRate, 0);
long autoFocusResetDelay = (long) a.getInteger(R.styleable.CameraView_cameraAutoFocusResetDelay, (int) DEFAULT_AUTOFOCUS_RESET_DELAY_MILLIS); long autoFocusResetDelay = (long) a.getInteger(R.styleable.CameraView_cameraAutoFocusResetDelay, (int) DEFAULT_AUTOFOCUS_RESET_DELAY_MILLIS);
// Picture size selector // Size selectors and gestures
List<SizeSelector> pictureConstraints = new ArrayList<>(3); SizeSelectorParser sizeSelectors = new SizeSelectorParser(a);
if (a.hasValue(R.styleable.CameraView_cameraPictureSizeMinWidth)) { GestureParser gestures = new GestureParser(a);
pictureConstraints.add(SizeSelectors.minWidth(a.getInteger(R.styleable.CameraView_cameraPictureSizeMinWidth, 0)));
}
if (a.hasValue(R.styleable.CameraView_cameraPictureSizeMaxWidth)) {
pictureConstraints.add(SizeSelectors.maxWidth(a.getInteger(R.styleable.CameraView_cameraPictureSizeMaxWidth, 0)));
}
if (a.hasValue(R.styleable.CameraView_cameraPictureSizeMinHeight)) {
pictureConstraints.add(SizeSelectors.minHeight(a.getInteger(R.styleable.CameraView_cameraPictureSizeMinHeight, 0)));
}
if (a.hasValue(R.styleable.CameraView_cameraPictureSizeMaxHeight)) {
pictureConstraints.add(SizeSelectors.maxHeight(a.getInteger(R.styleable.CameraView_cameraPictureSizeMaxHeight, 0)));
}
if (a.hasValue(R.styleable.CameraView_cameraPictureSizeMinArea)) {
pictureConstraints.add(SizeSelectors.minArea(a.getInteger(R.styleable.CameraView_cameraPictureSizeMinArea, 0)));
}
if (a.hasValue(R.styleable.CameraView_cameraPictureSizeMaxArea)) {
pictureConstraints.add(SizeSelectors.maxArea(a.getInteger(R.styleable.CameraView_cameraPictureSizeMaxArea, 0)));
}
if (a.hasValue(R.styleable.CameraView_cameraPictureSizeAspectRatio)) {
//noinspection ConstantConditions
pictureConstraints.add(SizeSelectors.aspectRatio(AspectRatio.parse(a.getString(R.styleable.CameraView_cameraPictureSizeAspectRatio)), 0));
}
if (a.getBoolean(R.styleable.CameraView_cameraPictureSizeSmallest, false)) pictureConstraints.add(SizeSelectors.smallest());
if (a.getBoolean(R.styleable.CameraView_cameraPictureSizeBiggest, false)) pictureConstraints.add(SizeSelectors.biggest());
SizeSelector pictureSelector = !pictureConstraints.isEmpty() ?
SizeSelectors.and(pictureConstraints.toArray(new SizeSelector[0])) :
SizeSelectors.biggest();
// Video size selector
List<SizeSelector> videoConstraints = new ArrayList<>(3);
if (a.hasValue(R.styleable.CameraView_cameraVideoSizeMinWidth)) {
videoConstraints.add(SizeSelectors.minWidth(a.getInteger(R.styleable.CameraView_cameraVideoSizeMinWidth, 0)));
}
if (a.hasValue(R.styleable.CameraView_cameraVideoSizeMaxWidth)) {
videoConstraints.add(SizeSelectors.maxWidth(a.getInteger(R.styleable.CameraView_cameraVideoSizeMaxWidth, 0)));
}
if (a.hasValue(R.styleable.CameraView_cameraVideoSizeMinHeight)) {
videoConstraints.add(SizeSelectors.minHeight(a.getInteger(R.styleable.CameraView_cameraVideoSizeMinHeight, 0)));
}
if (a.hasValue(R.styleable.CameraView_cameraVideoSizeMaxHeight)) {
videoConstraints.add(SizeSelectors.maxHeight(a.getInteger(R.styleable.CameraView_cameraVideoSizeMaxHeight, 0)));
}
if (a.hasValue(R.styleable.CameraView_cameraVideoSizeMinArea)) {
videoConstraints.add(SizeSelectors.minArea(a.getInteger(R.styleable.CameraView_cameraVideoSizeMinArea, 0)));
}
if (a.hasValue(R.styleable.CameraView_cameraVideoSizeMaxArea)) {
videoConstraints.add(SizeSelectors.maxArea(a.getInteger(R.styleable.CameraView_cameraVideoSizeMaxArea, 0)));
}
if (a.hasValue(R.styleable.CameraView_cameraVideoSizeAspectRatio)) {
//noinspection ConstantConditions
videoConstraints.add(SizeSelectors.aspectRatio(AspectRatio.parse(a.getString(R.styleable.CameraView_cameraVideoSizeAspectRatio)), 0));
}
if (a.getBoolean(R.styleable.CameraView_cameraVideoSizeSmallest, false)) videoConstraints.add(SizeSelectors.smallest());
if (a.getBoolean(R.styleable.CameraView_cameraVideoSizeBiggest, false)) videoConstraints.add(SizeSelectors.biggest());
SizeSelector videoSelector = !videoConstraints.isEmpty() ?
SizeSelectors.and(videoConstraints.toArray(new SizeSelector[0])) :
SizeSelectors.biggest();
// Gestures
GestureAction tapGesture = GestureAction.fromValue(a.getInteger(R.styleable.CameraView_cameraGestureTap, GestureAction.DEFAULT_TAP.value()));
GestureAction longTapGesture = GestureAction.fromValue(a.getInteger(R.styleable.CameraView_cameraGestureLongTap, GestureAction.DEFAULT_LONG_TAP.value()));
GestureAction pinchGesture = GestureAction.fromValue(a.getInteger(R.styleable.CameraView_cameraGesturePinch, GestureAction.DEFAULT_PINCH.value()));
GestureAction scrollHorizontalGesture = GestureAction.fromValue(a.getInteger(R.styleable.CameraView_cameraGestureScrollHorizontal, GestureAction.DEFAULT_SCROLL_HORIZONTAL.value()));
GestureAction scrollVerticalGesture = GestureAction.fromValue(a.getInteger(R.styleable.CameraView_cameraGestureScrollVertical, GestureAction.DEFAULT_SCROLL_VERTICAL.value()));
a.recycle(); a.recycle();
@ -206,29 +167,29 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
setPlaySounds(playSounds); setPlaySounds(playSounds);
// Apply camera controller params // Apply camera controller params
setFacing(facing); setFacing(controls.getFacing());
setFlash(flash); setFlash(controls.getFlash());
setMode(mode); setMode(controls.getMode());
setWhiteBalance(whiteBalance); setWhiteBalance(controls.getWhiteBalance());
setGrid(grid); setGrid(controls.getGrid());
setGridColor(gridColor); setGridColor(gridColor);
setHdr(hdr); setHdr(controls.getHdr());
setAudio(audio); setAudio(controls.getAudio());
setAudioBitRate(audioBitRate); setAudioBitRate(audioBitRate);
setPictureSize(pictureSelector); setPictureSize(sizeSelectors.getPictureSizeSelector());
setVideoSize(videoSelector); setVideoSize(sizeSelectors.getVideoSizeSelector());
setVideoCodec(codec); setVideoCodec(controls.getVideoCodec());
setVideoMaxSize(videoMaxSize); setVideoMaxSize(videoMaxSize);
setVideoMaxDuration(videoMaxDuration); setVideoMaxDuration(videoMaxDuration);
setVideoBitRate(videoBitRate); setVideoBitRate(videoBitRate);
setAutoFocusResetDelay(autoFocusResetDelay); setAutoFocusResetDelay(autoFocusResetDelay);
// Apply gestures // Apply gestures
mapGesture(Gesture.TAP, tapGesture); mapGesture(Gesture.TAP, gestures.getTapAction());
mapGesture(Gesture.LONG_TAP, longTapGesture); mapGesture(Gesture.LONG_TAP, gestures.getLongTapAction());
mapGesture(Gesture.PINCH, pinchGesture); mapGesture(Gesture.PINCH, gestures.getPinchAction());
mapGesture(Gesture.SCROLL_HORIZONTAL, scrollHorizontalGesture); mapGesture(Gesture.SCROLL_HORIZONTAL, gestures.getHorizontalScrollAction());
mapGesture(Gesture.SCROLL_VERTICAL, scrollVerticalGesture); mapGesture(Gesture.SCROLL_VERTICAL, gestures.getVerticalScrollAction());
if (!isInEditMode()) { if (!isInEditMode()) {
mOrientationHelper = new OrientationHelper(context, mCameraCallbacks); mOrientationHelper = new OrientationHelper(context, mCameraCallbacks);
@ -457,19 +418,19 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
mGestureMap.put(gesture, action); mGestureMap.put(gesture, action);
switch (gesture) { switch (gesture) {
case PINCH: case PINCH:
mPinchGestureLayout.enable(mGestureMap.get(Gesture.PINCH) != none); mPinchGestureLayout.setActive(mGestureMap.get(Gesture.PINCH) != none);
break; break;
case TAP: case TAP:
// case DOUBLE_TAP: // case DOUBLE_TAP:
case LONG_TAP: case LONG_TAP:
mTapGestureLayout.enable( mTapGestureLayout.setActive(
mGestureMap.get(Gesture.TAP) != none || mGestureMap.get(Gesture.TAP) != none ||
// mGestureMap.get(Gesture.DOUBLE_TAP) != none || // mGestureMap.get(Gesture.DOUBLE_TAP) != none ||
mGestureMap.get(Gesture.LONG_TAP) != none); mGestureMap.get(Gesture.LONG_TAP) != none);
break; break;
case SCROLL_HORIZONTAL: case SCROLL_HORIZONTAL:
case SCROLL_VERTICAL: case SCROLL_VERTICAL:
mScrollGestureLayout.enable( mScrollGestureLayout.setActive(
mGestureMap.get(Gesture.SCROLL_HORIZONTAL) != none || mGestureMap.get(Gesture.SCROLL_HORIZONTAL) != none ||
mGestureMap.get(Gesture.SCROLL_VERTICAL) != none); mGestureMap.get(Gesture.SCROLL_VERTICAL) != none);
break; break;
@ -532,7 +493,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
// (1) if it was mapped to some action (we check here) // (1) if it was mapped to some action (we check here)
// (2) if it's supported by the camera (CameraController checks) // (2) if it's supported by the camera (CameraController checks)
private void onGesture(GestureLayout source, @NonNull CameraOptions options) { private void onGesture(GestureLayout source, @NonNull CameraOptions options) {
Gesture gesture = source.getGestureType(); Gesture gesture = source.getGesture();
GestureAction action = mGestureMap.get(gesture); GestureAction action = mGestureMap.get(gesture);
PointF[] points = source.getPoints(); PointF[] points = source.getPoints();
float oldValue, newValue; float oldValue, newValue;
@ -549,7 +510,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
case ZOOM: case ZOOM:
oldValue = mCameraController.getZoomValue(); oldValue = mCameraController.getZoomValue();
newValue = source.scaleValue(oldValue, 0, 1); newValue = source.computeValue(oldValue, 0, 1);
if (newValue != oldValue) { if (newValue != oldValue) {
mCameraController.setZoom(newValue, points, true); mCameraController.setZoom(newValue, points, true);
} }
@ -559,7 +520,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
oldValue = mCameraController.getExposureCorrectionValue(); oldValue = mCameraController.getExposureCorrectionValue();
float minValue = options.getExposureCorrectionMinValue(); float minValue = options.getExposureCorrectionMinValue();
float maxValue = options.getExposureCorrectionMaxValue(); float maxValue = options.getExposureCorrectionMaxValue();
newValue = source.scaleValue(oldValue, minValue, maxValue); newValue = source.computeValue(oldValue, minValue, maxValue);
if (newValue != oldValue) { if (newValue != oldValue) {
float[] bounds = new float[]{minValue, maxValue}; float[] bounds = new float[]{minValue, maxValue};
mCameraController.setExposureCorrection(newValue, bounds, points, true); mCameraController.setExposureCorrection(newValue, bounds, points, true);

@ -1,7 +1,8 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview;
import com.otaliastudios.cameraview.size.Size;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/** /**
* A preview frame to be processed by {@link FrameProcessor}s. * A preview frame to be processed by {@link FrameProcessor}s.
@ -20,7 +21,7 @@ public class Frame {
mManager = manager; mManager = manager;
} }
boolean isAlive() { private boolean isAlive() {
return mData != null; return mData != null;
} }

@ -1,6 +1,8 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview;
import com.otaliastudios.cameraview.size.Size;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;

@ -3,6 +3,10 @@ package com.otaliastudios.cameraview;
import android.hardware.Camera; import android.hardware.Camera;
import android.media.CamcorderProfile; import android.media.CamcorderProfile;
import android.media.MediaRecorder; import android.media.MediaRecorder;
import com.otaliastudios.cameraview.controls.Audio;
import com.otaliastudios.cameraview.size.Size;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;

@ -1,10 +1,9 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview;
import android.hardware.Camera; import com.otaliastudios.cameraview.controls.Facing;
import android.media.MediaRecorder; import com.otaliastudios.cameraview.controls.Flash;
import android.os.Build; import com.otaliastudios.cameraview.controls.Hdr;
import com.otaliastudios.cameraview.controls.WhiteBalance;
import java.util.HashMap;
abstract class Mapper { abstract class Mapper {

@ -3,6 +3,11 @@ package com.otaliastudios.cameraview;
import android.hardware.Camera; import android.hardware.Camera;
import android.os.Build; 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; import java.util.HashMap;

@ -3,6 +3,9 @@ package com.otaliastudios.cameraview;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.location.Location; import android.location.Location;
import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.size.Size;
import java.io.File; import java.io.File;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;

@ -12,6 +12,9 @@ import android.opengl.EGL14;
import android.opengl.EGLContext; import android.opengl.EGLContext;
import android.opengl.Matrix; import android.opengl.Matrix;
import android.os.Build; import android.os.Build;
import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.size.AspectRatio;
import com.otaliastudios.cameraview.size.Size;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;

@ -4,6 +4,9 @@ import android.graphics.SurfaceTexture;
import android.opengl.EGL14; import android.opengl.EGL14;
import android.os.Build; import android.os.Build;
import com.otaliastudios.cameraview.controls.Audio;
import com.otaliastudios.cameraview.size.Size;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;

@ -1,6 +1,12 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview;
import android.location.Location; import android.location.Location;
import com.otaliastudios.cameraview.controls.Audio;
import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.controls.VideoCodec;
import com.otaliastudios.cameraview.size.Size;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; 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 androidx.annotation.Nullable;
/** /**

@ -1,4 +1,4 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview.controls;
/** /**
* Base interface for controls like {@link Audio}, * 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 android.content.Context;
import com.otaliastudios.cameraview.CameraUtils;
import com.otaliastudios.cameraview.CameraView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; 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; 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 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 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 androidx.annotation.Nullable;
import java.io.File; 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; 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; 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; import androidx.annotation.Nullable;
/** /**

@ -1,6 +1,8 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview.gesture;
import com.otaliastudios.cameraview.CameraView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.util.Arrays; import java.util.Arrays;
@ -19,65 +21,69 @@ public enum Gesture {
/** /**
* Pinch gesture, typically assigned to the zoom control. * Pinch gesture, typically assigned to the zoom control.
* This gesture can be mapped to: * This gesture can be mapped to continuous actions:
* *
* - {@link GestureAction#ZOOM} * - {@link GestureAction#ZOOM}
* - {@link GestureAction#EXPOSURE_CORRECTION} * - {@link GestureAction#EXPOSURE_CORRECTION}
* - {@link GestureAction#NONE} * - {@link GestureAction#NONE}
*/ */
PINCH(GestureAction.ZOOM, GestureAction.EXPOSURE_CORRECTION), PINCH(GestureType.CONTINUOUS),
/** /**
* Single tap gesture, typically assigned to the focus control. * Single tap gesture, typically assigned to the focus control.
* This gesture can be mapped to: * This gesture can be mapped to one shot actions:
* *
* - {@link GestureAction#FOCUS} * - {@link GestureAction#FOCUS}
* - {@link GestureAction#FOCUS_WITH_MARKER} * - {@link GestureAction#FOCUS_WITH_MARKER}
* - {@link GestureAction#CAPTURE} * - {@link GestureAction#CAPTURE}
* - {@link GestureAction#NONE} * - {@link GestureAction#NONE}
*/ */
TAP(GestureAction.FOCUS, GestureAction.FOCUS_WITH_MARKER, GestureAction.CAPTURE), TAP(GestureType.ONE_SHOT),
// DOUBLE_TAP(GestureAction.FOCUS, GestureAction.FOCUS_WITH_MARKER, GestureAction.CAPTURE),
/** /**
* Long tap gesture. * Long tap gesture.
* This gesture can be mapped to: * This gesture can be mapped to one shot actions:
* *
* - {@link GestureAction#FOCUS} * - {@link GestureAction#FOCUS}
* - {@link GestureAction#FOCUS_WITH_MARKER} * - {@link GestureAction#FOCUS_WITH_MARKER}
* - {@link GestureAction#CAPTURE} * - {@link GestureAction#CAPTURE}
* - {@link GestureAction#NONE} * - {@link GestureAction#NONE}
*/ */
LONG_TAP(GestureAction.FOCUS, GestureAction.FOCUS_WITH_MARKER, GestureAction.CAPTURE), LONG_TAP(GestureType.ONE_SHOT),
/** /**
* Horizontal scroll gesture. * Horizontal scroll gesture.
* This gesture can be mapped to: * This gesture can be mapped to continuous actions:
* *
* - {@link GestureAction#ZOOM} * - {@link GestureAction#ZOOM}
* - {@link GestureAction#EXPOSURE_CORRECTION} * - {@link GestureAction#EXPOSURE_CORRECTION}
* - {@link GestureAction#NONE} * - {@link GestureAction#NONE}
*/ */
SCROLL_HORIZONTAL(GestureAction.ZOOM, GestureAction.EXPOSURE_CORRECTION), SCROLL_HORIZONTAL(GestureType.CONTINUOUS),
/** /**
* Vertical scroll gesture. * Vertical scroll gesture.
* This gesture can be mapped to: * This gesture can be mapped to continuous actions:
* *
* - {@link GestureAction#ZOOM} * - {@link GestureAction#ZOOM}
* - {@link GestureAction#EXPOSURE_CORRECTION} * - {@link GestureAction#EXPOSURE_CORRECTION}
* - {@link GestureAction#NONE} * - {@link GestureAction#NONE}
*/ */
SCROLL_VERTICAL(GestureAction.ZOOM, GestureAction.EXPOSURE_CORRECTION); SCROLL_VERTICAL(GestureType.CONTINUOUS);
Gesture(GestureAction... controls) { Gesture(@NonNull GestureType type) {
mControls = Arrays.asList(controls); this.type = type;
} }
private List<GestureAction> mControls; private GestureType type;
boolean isAssignableTo(@NonNull GestureAction control) { /**
return control == GestureAction.NONE || mControls.contains(control); * Whether this gesture can be assigned to the given {@link GestureAction}.
* @param action the action to be checked
* @return true if assignable
*/
public boolean isAssignableTo(@NonNull GestureAction action) {
return action == GestureAction.NONE || action.type() == type;
} }
} }

@ -1,6 +1,9 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview.gesture;
import com.otaliastudios.cameraview.CameraView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
/** /**
@ -16,55 +19,55 @@ public enum GestureAction {
/** /**
* No action. This can be mapped to any gesture to disable it. * No action. This can be mapped to any gesture to disable it.
*/ */
NONE(0), NONE(0, GestureType.ONE_SHOT),
/** /**
* Auto focus control, typically assigned to the tap gesture. * Auto focus control, typically assigned to the tap gesture.
* This action can be mapped to: * This action can be mapped to one shot gestures:
* *
* - {@link Gesture#TAP} * - {@link Gesture#TAP}
* - {@link Gesture#LONG_TAP} * - {@link Gesture#LONG_TAP}
*/ */
FOCUS(1), FOCUS(1, GestureType.ONE_SHOT),
/** /**
* Auto focus control, typically assigned to the tap gesture. * Auto focus control, typically assigned to the tap gesture.
* On top of {@link #FOCUS}, this will draw a default marker on screen. * On top of {@link #FOCUS}, this will draw a default marker on screen.
* This action can be mapped to: * This action can be mapped to one shot gestures:
* *
* - {@link Gesture#TAP} * - {@link Gesture#TAP}
* - {@link Gesture#LONG_TAP} * - {@link Gesture#LONG_TAP}
*/ */
FOCUS_WITH_MARKER(2), FOCUS_WITH_MARKER(2, GestureType.ONE_SHOT),
/** /**
* When triggered, this action will fire a picture shoot. * When triggered, this action will fire a picture shoot.
* This action can be mapped to: * This action can be mapped to one shot gestures:
* *
* - {@link Gesture#TAP} * - {@link Gesture#TAP}
* - {@link Gesture#LONG_TAP} * - {@link Gesture#LONG_TAP}
*/ */
CAPTURE(3), CAPTURE(3, GestureType.ONE_SHOT),
/** /**
* Zoom control, typically assigned to the pinch gesture. * Zoom control, typically assigned to the pinch gesture.
* This action can be mapped to: * This action can be mapped to continuous gestures:
* *
* - {@link Gesture#PINCH} * - {@link Gesture#PINCH}
* - {@link Gesture#SCROLL_HORIZONTAL} * - {@link Gesture#SCROLL_HORIZONTAL}
* - {@link Gesture#SCROLL_VERTICAL} * - {@link Gesture#SCROLL_VERTICAL}
*/ */
ZOOM(4), ZOOM(4, GestureType.CONTINUOUS),
/** /**
* Exposure correction control. * Exposure correction control.
* This action can be mapped to: * This action can be mapped to continuous gestures:
* *
* - {@link Gesture#PINCH} * - {@link Gesture#PINCH}
* - {@link Gesture#SCROLL_HORIZONTAL} * - {@link Gesture#SCROLL_HORIZONTAL}
* - {@link Gesture#SCROLL_VERTICAL} * - {@link Gesture#SCROLL_VERTICAL}
*/ */
EXPOSURE_CORRECTION(5); EXPOSURE_CORRECTION(5, GestureType.CONTINUOUS);
final static GestureAction DEFAULT_PINCH = NONE; final static GestureAction DEFAULT_PINCH = NONE;
@ -74,15 +77,22 @@ public enum GestureAction {
final static GestureAction DEFAULT_SCROLL_VERTICAL = NONE; final static GestureAction DEFAULT_SCROLL_VERTICAL = NONE;
private int value; private int value;
private GestureType type;
GestureAction(int value) { GestureAction(int value, @NonNull GestureType type) {
this.value = value; this.value = value;
this.type = type;
} }
int value() { int value() {
return value; return value;
} }
@NonNull
GestureType type() {
return type;
}
@Nullable @Nullable
static GestureAction fromValue(int value) { static GestureAction fromValue(int value) {
GestureAction[] list = GestureAction.values(); GestureAction[] list = GestureAction.values();

@ -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,53 @@
package com.otaliastudios.cameraview.gesture;
import android.content.Context;
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,31 @@
package com.otaliastudios.cameraview.gesture;
import com.otaliastudios.cameraview.CameraView;
import java.util.Arrays;
import java.util.List;
import androidx.annotation.NonNull;
/**
* 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,4 +1,4 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview.gesture;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
@ -8,22 +8,20 @@ import androidx.annotation.NonNull;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.ScaleGestureDetector; import android.view.ScaleGestureDetector;
class PinchGestureLayout extends GestureLayout { /**
* A {@link GestureLayout} that detects {@link Gesture#PINCH} gestures.
*/
public class PinchGestureLayout extends GestureLayout {
private final static float ADD_SENSITIVITY = 2f; private final static float ADD_SENSITIVITY = 2f;
ScaleGestureDetector mDetector; ScaleGestureDetector mDetector;
private boolean mNotify; private boolean mNotify;
/* tests */ float mFactor = 0; private float mFactor = 0;
public PinchGestureLayout(@NonNull Context context) { public PinchGestureLayout(@NonNull Context context) {
super(context); super(context, 2);
} setGesture(Gesture.PINCH);
@Override
protected void onInitialize(@NonNull Context context) {
super.onInitialize(context);
mPoints = new PointF[]{ new PointF(0, 0), new PointF(0, 0) };
mDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.SimpleOnScaleGestureListener() { mDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.SimpleOnScaleGestureListener() {
@Override @Override
public boolean onScale(ScaleGestureDetector detector) { public boolean onScale(ScaleGestureDetector detector) {
@ -36,17 +34,10 @@ class PinchGestureLayout extends GestureLayout {
if (Build.VERSION.SDK_INT >= 19) { if (Build.VERSION.SDK_INT >= 19) {
mDetector.setQuickScaleEnabled(false); mDetector.setQuickScaleEnabled(false);
} }
// We listen only to the pinch type.
mType = Gesture.PINCH;
} }
@SuppressLint("ClickableViewAccessibility")
@Override @Override
public boolean onTouchEvent(MotionEvent event) { protected boolean handleTouchEvent(@NonNull MotionEvent event) {
if (!mEnabled) return false;
// Reset the mNotify flag on a new gesture. // Reset the mNotify flag on a new gesture.
// This is to ensure that the mNotify flag stays on until the // This is to ensure that the mNotify flag stays on until the
// previous gesture ends. // previous gesture ends.
@ -59,11 +50,11 @@ class PinchGestureLayout extends GestureLayout {
// Keep notifying CameraView as long as the gesture goes. // Keep notifying CameraView as long as the gesture goes.
if (mNotify) { if (mNotify) {
mPoints[0].x = event.getX(0); getPoint(0).x = event.getX(0);
mPoints[0].y = event.getY(0); getPoint(0).y = event.getY(0);
if (event.getPointerCount() > 1) { if (event.getPointerCount() > 1) {
mPoints[1].x = event.getX(1); getPoint(1).x = event.getX(1);
mPoints[1].y = event.getY(1); getPoint(1).y = event.getY(1);
} }
return true; return true;
} }
@ -71,8 +62,8 @@ class PinchGestureLayout extends GestureLayout {
} }
@Override @Override
public float scaleValue(float currValue, float minValue, float maxValue) { public float getValue(float currValue, float minValue, float maxValue) {
float add = mFactor; float add = getFactor();
// ^ This works well if minValue = 0, maxValue = 1. // ^ This works well if minValue = 0, maxValue = 1.
// Account for the different range: // Account for the different range:
add *= (maxValue - minValue); add *= (maxValue - minValue);
@ -84,6 +75,11 @@ class PinchGestureLayout extends GestureLayout {
} else if (add < 0) { } else if (add < 0) {
add *= (currValue - minValue); add *= (currValue - minValue);
} Nope, I don't like this, it slows everything down. */ } Nope, I don't like this, it slows everything down. */
return capValue(currValue, currValue + add, minValue, maxValue); return currValue + add;
}
/* for tests */ protected float getFactor() {
return mFactor;
} }
} }

@ -1,4 +1,4 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview.gesture;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
@ -7,23 +7,23 @@ import androidx.annotation.NonNull;
import android.view.GestureDetector; import android.view.GestureDetector;
import android.view.MotionEvent; import android.view.MotionEvent;
class ScrollGestureLayout extends GestureLayout { import com.otaliastudios.cameraview.CameraLogger;
/**
* A {@link GestureLayout} that detects {@link Gesture#SCROLL_HORIZONTAL}
* and {@link Gesture#SCROLL_VERTICAL} gestures.
*/
public class ScrollGestureLayout extends GestureLayout {
private static final String TAG = ScrollGestureLayout.class.getSimpleName(); private static final String TAG = ScrollGestureLayout.class.getSimpleName();
private static final CameraLogger LOG = CameraLogger.create(TAG); private static final CameraLogger LOG = CameraLogger.create(TAG);
private GestureDetector mDetector; private GestureDetector mDetector;
private boolean mNotify; private boolean mNotify;
/* tests */ float mFactor; private float mFactor;
public ScrollGestureLayout(@NonNull Context context) { public ScrollGestureLayout(@NonNull Context context) {
super(context); super(context, 2);
}
@Override
protected void onInitialize(@NonNull Context context) {
super.onInitialize(context);
mPoints = new PointF[]{ new PointF(0, 0), new PointF(0, 0) };
mDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { mDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override @Override
@ -31,17 +31,17 @@ class ScrollGestureLayout extends GestureLayout {
boolean horizontal; boolean horizontal;
LOG.i("onScroll:", "distanceX="+distanceX, "distanceY="+distanceY); LOG.i("onScroll:", "distanceX="+distanceX, "distanceY="+distanceY);
if (e1 == null || e2 == null) return false; // Got some crashes about this. if (e1 == null || e2 == null) return false; // Got some crashes about this.
if (e1.getX() != mPoints[0].x || e1.getY() != mPoints[0].y) { if (e1.getX() != getPoint(0).x || e1.getY() != getPoint(0).y) {
// First step. We choose now if it's a vertical or horizontal scroll, and // First step. We choose now if it's a vertical or horizontal scroll, and
// stick to it for the whole gesture. // stick to it for the whole gesture.
horizontal = Math.abs(distanceX) >= Math.abs(distanceY); horizontal = Math.abs(distanceX) >= Math.abs(distanceY);
mType = horizontal ? Gesture.SCROLL_HORIZONTAL : Gesture.SCROLL_VERTICAL; setGesture(horizontal ? Gesture.SCROLL_HORIZONTAL : Gesture.SCROLL_VERTICAL);
mPoints[0].set(e1.getX(), e1.getY()); getPoint(0).set(e1.getX(), e1.getY());
} else { } else {
// Not the first step. We already defined the type. // Not the first step. We already defined the type.
horizontal = mType == Gesture.SCROLL_HORIZONTAL; horizontal = getGesture() == Gesture.SCROLL_HORIZONTAL;
} }
mPoints[1].set(e2.getX(), e2.getY()); getPoint(1).set(e2.getX(), e2.getY());
mFactor = horizontal ? (distanceX / getWidth()) : (distanceY / getHeight()); mFactor = horizontal ? (distanceX / getWidth()) : (distanceY / getHeight());
mFactor = horizontal ? -mFactor : mFactor; // When vertical, up = positive mFactor = horizontal ? -mFactor : mFactor; // When vertical, up = positive
mNotify = true; mNotify = true;
@ -52,11 +52,8 @@ class ScrollGestureLayout extends GestureLayout {
mDetector.setIsLongpressEnabled(false); // Looks important. mDetector.setIsLongpressEnabled(false); // Looks important.
} }
@SuppressLint("ClickableViewAccessibility")
@Override @Override
public boolean onTouchEvent(MotionEvent event) { protected boolean handleTouchEvent(@NonNull MotionEvent event) {
if (!mEnabled) return false;
// Reset the mNotify flag on a new gesture. // Reset the mNotify flag on a new gesture.
// This is to ensure that the mNotify flag stays on until the // This is to ensure that the mNotify flag stays on until the
// previous gesture ends. // previous gesture ends.
@ -68,20 +65,23 @@ class ScrollGestureLayout extends GestureLayout {
mDetector.onTouchEvent(event); mDetector.onTouchEvent(event);
// Keep notifying CameraView as long as the gesture goes. // Keep notifying CameraView as long as the gesture goes.
if (mNotify) LOG.i("Notifying a gesture of type", mType.name()); if (mNotify) LOG.i("Notifying a gesture of type", getGesture().name());
return mNotify; return mNotify;
} }
@Override @Override
public float scaleValue(float currValue, float minValue, float maxValue) { public float getValue(float currValue, float minValue, float maxValue) {
float delta = mFactor; // -1 ... 1 float delta = getFactor(); // -1 ... 1
// ^ This works well if minValue = 0, maxValue = 1. // ^ This works well if minValue = 0, maxValue = 1.
// Account for the different range: // Account for the different range:
delta *= (maxValue - minValue); // -(max-min) ... (max-min) delta *= (maxValue - minValue); // -(max-min) ... (max-min)
delta *= 2; // Add some sensitivity. delta *= 2; // Add some sensitivity.
return capValue(currValue, currValue + delta, minValue, maxValue); return currValue + delta;
} }
/* for tests */ protected float getFactor() {
return mFactor;
}
} }

@ -1,4 +1,4 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview.gesture;
import android.animation.Animator; import android.animation.Animator;
import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorListenerAdapter;
@ -14,7 +14,13 @@ import android.view.View;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.ImageView; import android.widget.ImageView;
class TapGestureLayout extends GestureLayout { import com.otaliastudios.cameraview.R;
/**
* A {@link GestureLayout} that detects {@link Gesture#TAP}
* and {@link Gesture#LONG_TAP} gestures.
*/
public class TapGestureLayout extends GestureLayout {
private GestureDetector mDetector; private GestureDetector mDetector;
private boolean mNotify; private boolean mNotify;
@ -23,19 +29,13 @@ class TapGestureLayout extends GestureLayout {
private ImageView mFocusMarkerFill; private ImageView mFocusMarkerFill;
public TapGestureLayout(@NonNull Context context) { public TapGestureLayout(@NonNull Context context) {
super(context); super(context, 1);
}
@Override
protected void onInitialize(@NonNull Context context) {
super.onInitialize(context);
mPoints = new PointF[]{ new PointF(0, 0) };
mDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { mDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override @Override
public boolean onSingleTapUp(MotionEvent e) { public boolean onSingleTapUp(MotionEvent e) {
mNotify = true; mNotify = true;
mType = Gesture.TAP; setGesture(Gesture.TAP);
return true; return true;
} }
@ -50,7 +50,7 @@ class TapGestureLayout extends GestureLayout {
@Override @Override
public void onLongPress(MotionEvent e) { public void onLongPress(MotionEvent e) {
mNotify = true; mNotify = true;
mType = Gesture.LONG_TAP; setGesture(Gesture.LONG_TAP);
} }
}); });
@ -63,11 +63,8 @@ class TapGestureLayout extends GestureLayout {
mFocusMarkerFill = findViewById(R.id.fill); mFocusMarkerFill = findViewById(R.id.fill);
} }
@SuppressLint("ClickableViewAccessibility")
@Override @Override
public boolean onTouchEvent(MotionEvent event) { protected boolean handleTouchEvent(@NonNull MotionEvent event) {
if (!mEnabled) return false;
// Reset the mNotify flag on a new gesture. // Reset the mNotify flag on a new gesture.
// This is to ensure that the mNotify flag stays on until the // This is to ensure that the mNotify flag stays on until the
// previous gesture ends. // previous gesture ends.
@ -80,15 +77,15 @@ class TapGestureLayout extends GestureLayout {
// Keep notifying CameraView as long as the gesture goes. // Keep notifying CameraView as long as the gesture goes.
if (mNotify) { if (mNotify) {
mPoints[0].x = event.getX(); getPoint(0).x = event.getX();
mPoints[0].y = event.getY(); getPoint(0).y = event.getY();
return true; return true;
} }
return false; return false;
} }
@Override @Override
public float scaleValue(float currValue, float minValue, float maxValue) { public float getValue(float currValue, float minValue, float maxValue) {
return 0; return 0;
} }

@ -1,4 +1,4 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview.size;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -10,7 +10,7 @@ import java.util.HashMap;
*/ */
public class AspectRatio implements Comparable<AspectRatio> { public class AspectRatio implements Comparable<AspectRatio> {
final static HashMap<String, AspectRatio> sCache = new HashMap<>(16); private final static HashMap<String, AspectRatio> sCache = new HashMap<>(16);
/** /**
* Creates an aspect ratio for the given size. * Creates an aspect ratio for the given size.
@ -125,9 +125,13 @@ public class AspectRatio implements Comparable<AspectRatio> {
return -1; return -1;
} }
/**
* Returns a flipped aspect ratio, which means inverting its dimensions.
* @return a flipped aspect ratio
*/
@SuppressWarnings("SuspiciousNameCombination") @SuppressWarnings("SuspiciousNameCombination")
@NonNull @NonNull
public AspectRatio inverse() { public AspectRatio flip() {
return AspectRatio.of(mY, mX); return AspectRatio.of(mY, mX);
} }

@ -1,4 +1,4 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview.size;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -10,7 +10,7 @@ public class Size implements Comparable<Size> {
private final int mWidth; private final int mWidth;
private final int mHeight; private final int mHeight;
Size(int width, int height) { public Size(int width, int height) {
mWidth = width; mWidth = width;
mHeight = height; mHeight = height;
} }
@ -23,8 +23,14 @@ public class Size implements Comparable<Size> {
return mHeight; return mHeight;
} }
/**
* Returns a flipped size, with height equal to this size's width
* and width equal to this size's height.
*
* @return a flipped size
*/
@SuppressWarnings("SuspiciousNameCombination") @SuppressWarnings("SuspiciousNameCombination")
Size flip() { public Size flip() {
return new Size(mHeight, mWidth); return new Size(mHeight, mWidth);
} }

@ -1,4 +1,6 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview.size;
import com.otaliastudios.cameraview.size.Size;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;

@ -0,0 +1,102 @@
package com.otaliastudios.cameraview.size;
import android.content.Context;
import android.content.res.TypedArray;
import com.otaliastudios.cameraview.R;
import com.otaliastudios.cameraview.controls.Audio;
import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.controls.Flash;
import com.otaliastudios.cameraview.controls.Grid;
import com.otaliastudios.cameraview.controls.Hdr;
import com.otaliastudios.cameraview.controls.Mode;
import com.otaliastudios.cameraview.controls.Preview;
import com.otaliastudios.cameraview.controls.VideoCodec;
import com.otaliastudios.cameraview.controls.WhiteBalance;
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,8 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview.size;
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.NonNull;

@ -71,7 +71,14 @@ public final class CameraLogger {
}); });
} }
static CameraLogger create(@NonNull String tag) { /**
* Creates a CameraLogger that will stream logs into the
* internal logs and dispatch them to {@link Logger}s.
*
* @param tag the logger tag
* @return a new CameraLogger
*/
public static CameraLogger create(@NonNull String tag) {
return new CameraLogger(tag); return new CameraLogger(tag);
} }
@ -121,18 +128,34 @@ public final class CameraLogger {
return sLevel <= messageLevel && sLoggers.size() > 0; return sLevel <= messageLevel && sLoggers.size() > 0;
} }
void v(@NonNull Object... data) { /**
* Log to the verbose channel.
* @param data log contents
*/
public void v(@NonNull Object... data) {
log(LEVEL_VERBOSE, data); log(LEVEL_VERBOSE, data);
} }
void i(@NonNull Object... data) { /**
* Log to the info channel.
* @param data log contents
*/
public void i(@NonNull Object... data) {
log(LEVEL_INFO, data); log(LEVEL_INFO, data);
} }
/**
* Log to the warning channel.
* @param data log contents
*/
void w(@NonNull Object... data) { void w(@NonNull Object... data) {
log(LEVEL_WARNING, data); log(LEVEL_WARNING, data);
} }
/**
* Log to the error channel.
* @param data log contents
*/
void e(@NonNull Object... data) { void e(@NonNull Object... data) {
log(LEVEL_ERROR, data); log(LEVEL_ERROR, data);
} }

@ -7,6 +7,9 @@ import android.graphics.BitmapFactory;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.hardware.Camera; import android.hardware.Camera;
import android.os.Handler; import android.os.Handler;
import com.otaliastudios.cameraview.controls.Facing;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread; import androidx.annotation.WorkerThread;
@ -15,7 +18,6 @@ import androidx.exifinterface.media.ExifInterface;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;

@ -1,6 +1,10 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview;
import android.graphics.Rect; import android.graphics.Rect;
import com.otaliastudios.cameraview.size.AspectRatio;
import com.otaliastudios.cameraview.size.Size;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
class CropHelper { class CropHelper {

@ -1,5 +1,7 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview;
import com.otaliastudios.cameraview.size.Size;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
/** /**

@ -6,6 +6,8 @@ import androidx.annotation.Nullable;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.otaliastudios.cameraview.size.Size;
/** /**
* A CameraPreview takes in input stream from the {@link CameraController}, and streams it * A CameraPreview takes in input stream from the {@link CameraController}, and streams it
* into an output surface that belongs to the view hierarchy. * into an output surface that belongs to the view hierarchy.

@ -1,70 +0,0 @@
package com.otaliastudios.cameraview;
import android.content.Context;
import android.graphics.PointF;
import androidx.annotation.NonNull;
import android.view.MotionEvent;
import android.widget.FrameLayout;
abstract class GestureLayout extends FrameLayout {
// The number of possible values between minValue and maxValue, for the scaleValue method.
// We could make this non-static (e.g. larger granularity for exposure correction).
private final static int GRANULARITY = 50;
protected boolean mEnabled;
protected Gesture mType;
protected PointF[] mPoints;
public GestureLayout(@NonNull Context context) {
super(context);
onInitialize(context);
}
protected void onInitialize(@NonNull Context context) {
}
public void enable(boolean enable) {
mEnabled = enable;
}
public boolean enabled() {
return mEnabled;
}
public abstract boolean onTouchEvent(MotionEvent event);
@NonNull
public final Gesture getGestureType() {
return mType;
}
// For tests.
void setGestureType(@NonNull Gesture type) {
mType = type;
}
@NonNull
public final PointF[] getPoints() {
return mPoints;
}
// Implementors should call capValue at the end.
public abstract float scaleValue(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.
protected 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;
}
}

@ -12,6 +12,8 @@ import android.view.SurfaceHolder;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.otaliastudios.cameraview.size.AspectRatio;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;

@ -12,6 +12,8 @@ import android.util.AttributeSet;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.View; import android.view.View;
import com.otaliastudios.cameraview.controls.Grid;
class GridLinesLayout extends View { class GridLinesLayout extends View {
private final static float GOLDEN_RATIO_INV = 0.61803398874989f; private final static float GOLDEN_RATIO_INV = 0.61803398874989f;

@ -10,6 +10,8 @@ import android.view.TextureView;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.otaliastudios.cameraview.size.AspectRatio;
class TextureCameraPreview extends CameraPreview<TextureView, SurfaceTexture> { class TextureCameraPreview extends CameraPreview<TextureView, SurfaceTexture> {
private View mRootView; private View mRootView;

@ -1,6 +1,9 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview;
import com.otaliastudios.cameraview.size.AspectRatio;
import com.otaliastudios.cameraview.size.Size;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@ -47,7 +50,7 @@ public class AspectRatioTest {
@Test @Test
public void testInverse() { public void testInverse() {
AspectRatio ratio = AspectRatio.of(50, 10); AspectRatio ratio = AspectRatio.of(50, 10);
AspectRatio inverse = ratio.inverse(); AspectRatio inverse = ratio.flip();
assertEquals(inverse.getX(), 1f, 0); assertEquals(inverse.getX(), 1f, 0);
assertEquals(inverse.getY(), 5f, 0); assertEquals(inverse.getY(), 5f, 0);
} }

@ -1,13 +1,11 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview;
import android.graphics.ImageFormat; import com.otaliastudios.cameraview.size.Size;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@ -15,13 +13,11 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset; import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class FrameManagerTest { public class FrameManagerTest {

@ -3,10 +3,11 @@ package com.otaliastudios.cameraview;
import android.graphics.ImageFormat; import android.graphics.ImageFormat;
import com.otaliastudios.cameraview.size.Size;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mockito;
import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNotNull;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;

@ -1,6 +1,11 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview;
import com.otaliastudios.cameraview.size.AspectRatio;
import com.otaliastudios.cameraview.size.Size;
import com.otaliastudios.cameraview.size.SizeSelector;
import com.otaliastudios.cameraview.size.SizeSelectors;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.junit.Before; import org.junit.Before;

@ -1,6 +1,8 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview;
import com.otaliastudios.cameraview.size.Size;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.*; import static org.junit.Assert.*;

@ -2,13 +2,11 @@ package com.otaliastudios.cameraview.demo;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.bottomsheet.BottomSheetBehavior;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import android.util.Log;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
@ -19,10 +17,8 @@ import com.otaliastudios.cameraview.CameraListener;
import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.CameraOptions; import com.otaliastudios.cameraview.CameraOptions;
import com.otaliastudios.cameraview.CameraView; import com.otaliastudios.cameraview.CameraView;
import com.otaliastudios.cameraview.Frame;
import com.otaliastudios.cameraview.FrameProcessor;
import com.otaliastudios.cameraview.PictureResult; import com.otaliastudios.cameraview.PictureResult;
import com.otaliastudios.cameraview.Mode; import com.otaliastudios.cameraview.controls.Mode;
import com.otaliastudios.cameraview.VideoResult; import com.otaliastudios.cameraview.VideoResult;
import java.io.File; import java.io.File;

@ -5,17 +5,17 @@ import androidx.annotation.NonNull;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.otaliastudios.cameraview.Audio; import com.otaliastudios.cameraview.controls.Audio;
import com.otaliastudios.cameraview.CameraOptions; import com.otaliastudios.cameraview.CameraOptions;
import com.otaliastudios.cameraview.CameraView; import com.otaliastudios.cameraview.CameraView;
import com.otaliastudios.cameraview.Flash; import com.otaliastudios.cameraview.controls.Flash;
import com.otaliastudios.cameraview.Gesture; import com.otaliastudios.cameraview.gesture.Gesture;
import com.otaliastudios.cameraview.GestureAction; import com.otaliastudios.cameraview.gesture.GestureAction;
import com.otaliastudios.cameraview.Grid; import com.otaliastudios.cameraview.controls.Grid;
import com.otaliastudios.cameraview.Hdr; import com.otaliastudios.cameraview.controls.Hdr;
import com.otaliastudios.cameraview.Mode; import com.otaliastudios.cameraview.controls.Mode;
import com.otaliastudios.cameraview.VideoCodec; import com.otaliastudios.cameraview.controls.VideoCodec;
import com.otaliastudios.cameraview.WhiteBalance; import com.otaliastudios.cameraview.controls.WhiteBalance;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -157,7 +157,7 @@ public enum Control {
case AUDIO: case AUDIO:
case VIDEO_CODEC: case VIDEO_CODEC:
case HDR: case HDR:
camera.set((com.otaliastudios.cameraview.Control) value); camera.set((com.otaliastudios.cameraview.controls.Control) value);
break; break;
case PINCH: case PINCH:
camera.mapGesture(Gesture.PINCH, (GestureAction) value); camera.mapGesture(Gesture.PINCH, (GestureAction) value);

@ -9,7 +9,7 @@ import androidx.annotation.Nullable;
import android.util.Log; import android.util.Log;
import android.widget.ImageView; import android.widget.ImageView;
import com.otaliastudios.cameraview.AspectRatio; import com.otaliastudios.cameraview.size.AspectRatio;
import com.otaliastudios.cameraview.BitmapCallback; import com.otaliastudios.cameraview.BitmapCallback;
import com.otaliastudios.cameraview.PictureResult; import com.otaliastudios.cameraview.PictureResult;

Loading…
Cancel
Save