Skip to content

Commit

Permalink
Pinch to zoom (RedApparat#388)
Browse files Browse the repository at this point in the history
* Update gradle and libs

* Implemented pinch to zoom in example app
  • Loading branch information
ivan200 authored Dec 10, 2021
1 parent 09a5516 commit 7039cf2
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 43 deletions.
14 changes: 5 additions & 9 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ subprojects {
buildscript {
ext {
versions = [
gradle : '4.10.2',
kotlin : '1.3.0',
kotlin : '1.3.50',
code : 1,
name : '1.0.0',
sdk : [
Expand All @@ -19,13 +18,13 @@ buildscript {
],
android: [
buildTools: '28.0.3',
appcompat : '1.0.1',
annotation : '1.0.0',
appcompat : '1.1.0',
annotation : '1.1.0',
exifinterface : '1.0.0'
],
rx : [
rxJava1: '1.3.8',
rxJava2: '2.2.3'
rxJava2: '2.2.12'
],
test : [
junit : '4.12',
Expand All @@ -38,7 +37,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.0'
classpath 'com.android.tools.build:gradle:3.5.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"

classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
Expand All @@ -57,6 +56,3 @@ task clean(type: Delete) {
delete rootProject.buildDir
}

task wrapper(type: Wrapper) {
gradleVersion = versions.gradle
}
2 changes: 1 addition & 1 deletion fotoapparat/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ dependencies {
implementation "androidx.annotation:annotation:${versions.android.annotation}"
implementation "androidx.exifinterface:exifinterface:${versions.android.exifinterface}"
implementation "org.jetbrains.kotlin:kotlin-stdlib:${versions.kotlin}"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0'
testImplementation "junit:junit:${versions.test.junit}"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:${versions.kotlin}"
testImplementation "org.mockito:mockito-core:${versions.test.mockito}"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package io.fotoapparat.coroutines

import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.BroadcastChannel
import kotlinx.coroutines.channels.ConflatedBroadcastChannel

/**
* A [ConflatedBroadcastChannel] which exposes a [getValue] which will [await] for at least one value.
*/
@ExperimentalCoroutinesApi
internal class AwaitBroadcastChannel<T>(
private val channel: ConflatedBroadcastChannel<T> = ConflatedBroadcastChannel(),
private val deferred: CompletableDeferred<Boolean> = CompletableDeferred()
Expand All @@ -31,7 +31,13 @@ internal class AwaitBroadcastChannel<T>(
channel.send(element)
}

override fun cancel(cause: CancellationException?) {
channel.cancel(cause)
deferred.cancel(cause)
}

override fun cancel(cause: Throwable?): Boolean {
return channel.cancel(cause) && deferred.cancel(cause)
deferred.cancel(cause?.message ?:"", cause)
return channel.close(cause)
}
}
31 changes: 30 additions & 1 deletion fotoapparat/src/main/java/io/fotoapparat/view/FocusView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.content.Context
import android.util.AttributeSet
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import android.widget.FrameLayout
import io.fotoapparat.hardware.metering.FocalRequest
import io.fotoapparat.hardware.metering.PointF
Expand All @@ -15,7 +16,7 @@ import io.fotoapparat.parameter.Resolution
*
* If the camera doesn't support focus metering on specific area this will only display a visual feedback.
*/
class FocusView
open class FocusView
@JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
Expand All @@ -25,6 +26,15 @@ class FocusView
private val visualFeedbackCircle = FeedbackCircleView(context, attrs, defStyleAttr)
private var focusMeteringListener: ((FocalRequest) -> Unit)? = null

var scaleListener: ((Float) -> Unit)? = null
var ptrListener: ((Int) -> Unit)? = null

private var mPtrCount: Int = 0
set(value) {
field = value
ptrListener?.invoke(value)
}

init {
clipToPadding = false
clipChildren = false
Expand All @@ -38,6 +48,14 @@ class FocusView
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
tapDetector.onTouchEvent(event)
scaleDetector.onTouchEvent(event)

when (event.action and MotionEvent.ACTION_MASK) {
MotionEvent.ACTION_POINTER_DOWN -> mPtrCount++
MotionEvent.ACTION_POINTER_UP -> mPtrCount--
MotionEvent.ACTION_DOWN -> mPtrCount++
MotionEvent.ACTION_UP -> mPtrCount--
}
return true
}

Expand All @@ -64,4 +82,15 @@ class FocusView
}

private val tapDetector = GestureDetector(context, gestureDetectorListener)

private val scaleGestureDetector = object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(detector: ScaleGestureDetector): Boolean {
return scaleListener
?.let {
it(detector.scaleFactor)
true
}?: super.onScale(detector)
}
}
private val scaleDetector = ScaleGestureDetector(context, scaleGestureDetector)
}
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
80 changes: 72 additions & 8 deletions sample/src/main/java/io/fotoapparat/sample/ActivityJava.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,35 @@
import android.view.View;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.util.Locale;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SwitchCompat;
import io.fotoapparat.Fotoapparat;
import io.fotoapparat.capability.Capabilities;
import io.fotoapparat.configuration.CameraConfiguration;
import io.fotoapparat.configuration.UpdateConfiguration;
import io.fotoapparat.error.CameraErrorListener;
import io.fotoapparat.exception.camera.CameraException;
import io.fotoapparat.parameter.ScaleType;
import io.fotoapparat.parameter.Zoom;
import io.fotoapparat.preview.Frame;
import io.fotoapparat.preview.FrameProcessor;
import io.fotoapparat.result.BitmapPhoto;
import io.fotoapparat.result.PhotoResult;
import io.fotoapparat.result.WhenDoneListener;
import io.fotoapparat.view.CameraView;
import io.fotoapparat.view.FocusView;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;

import static io.fotoapparat.log.LoggersKt.fileLogger;
import static io.fotoapparat.log.LoggersKt.logcat;
Expand Down Expand Up @@ -57,9 +62,13 @@ public class ActivityJava extends AppCompatActivity {
private boolean hasCameraPermission;
private CameraView cameraView;
private FocusView focusView;
private TextView zoomLvl;
private ImageView switchCamera;
private View capture;

private Fotoapparat fotoapparat;
private Zoom.VariableZoom cameraZoom;
private float curZoom = 0f;

boolean activeCameraBack = true;

Expand Down Expand Up @@ -92,6 +101,8 @@ protected void onCreate(Bundle savedInstanceState) {
cameraView = findViewById(R.id.cameraView);
focusView = findViewById(R.id.focusView);
capture = findViewById(R.id.capture);
zoomLvl = findViewById(R.id.zoomLvl);
switchCamera = findViewById(R.id.switchCamera);
hasCameraPermission = permissionsDelegate.hasCameraPermission();

if (hasCameraPermission) {
Expand All @@ -105,7 +116,6 @@ protected void onCreate(Bundle savedInstanceState) {
takePictureOnClick();
switchCameraOnClick();
toggleTorchOnSwitch();
zoomSeekBar();
}

private Fotoapparat createFotoapparat() {
Expand All @@ -129,15 +139,66 @@ public void onError(@NotNull CameraException e) {
.build();
}

private void zoomSeekBar() {
SeekBar seekBar = findViewById(R.id.zoomSeekBar);

seekBar.setOnSeekBarChangeListener(new OnProgressChanged() {
private void adjustViewsVisibility() {
fotoapparat.getCapabilities().whenAvailable(new Function1<Capabilities, Unit>() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
fotoapparat.setZoom(progress / (float) seekBar.getMax());
public Unit invoke(Capabilities capabilities) {
Zoom zoom = capabilities != null ? capabilities.getZoom() : null;
if(zoom instanceof Zoom.VariableZoom){
cameraZoom = (Zoom.VariableZoom) zoom;
focusView.setScaleListener(new Function1<Float, Unit>() {
@Override
public Unit invoke(Float aFloat) {
scaleZoom(aFloat);
return null;
}
});
focusView.setPtrListener(new Function1<Integer, Unit>() {
@Override
public Unit invoke(Integer integer) {
pointerChanged(integer);
return null;
}
});
} else {
zoomLvl.setVisibility(View.GONE);
focusView.setScaleListener(null);
focusView.setPtrListener(null);
}
return null;
}
});
if (fotoapparat.isAvailable(front())){
switchCamera.setVisibility(View.VISIBLE);
} else {
switchCamera.setVisibility(View.GONE);
}
}

private void scaleZoom(float scaleFactor){
float plusZoom = 0;
if (scaleFactor < 1) plusZoom = -1 * (1 - scaleFactor);
else plusZoom = scaleFactor - 1;

float newZoom = curZoom + plusZoom;
if (newZoom < 0 || newZoom > 1) return;

curZoom = newZoom;
fotoapparat.setZoom(curZoom);

int progress = Math.round (cameraZoom.getMaxZoom() * curZoom);
int value = cameraZoom.getZoomRatios().get(progress);

float roundedValue = (float)(Math.round(((float)value) / 10f)) / 10f;

zoomLvl.setVisibility(View.VISIBLE);
zoomLvl.setText(String.format(Locale.getDefault(), "%.1f×", roundedValue));
}

private void pointerChanged(int fingerCount){
if(fingerCount == 0) {
zoomLvl.setVisibility(View.GONE);
}
}

private void switchCameraOnClick() {
Expand Down Expand Up @@ -180,6 +241,7 @@ public void onClick(View v) {
activeCameraBack ? back() : front(),
cameraConfiguration
);
adjustViewsVisibility();
}
});
}
Expand Down Expand Up @@ -223,6 +285,7 @@ protected void onStart() {
super.onStart();
if (hasCameraPermission) {
fotoapparat.start();
adjustViewsVisibility();
}
}

Expand All @@ -242,6 +305,7 @@ public void onRequestPermissionsResult(int requestCode,
if (permissionsDelegate.resultGranted(requestCode, permissions, grantResults)) {
hasCameraPermission = true;
fotoapparat.start();
adjustViewsVisibility();
cameraView.setVisibility(View.VISIBLE);
}
}
Expand Down
42 changes: 30 additions & 12 deletions sample/src/main/java/io/fotoapparat/sample/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class MainActivity : AppCompatActivity() {
private lateinit var fotoapparat: Fotoapparat
private lateinit var cameraZoom: Zoom.VariableZoom

private var curZoom: Float = 0f

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Expand Down Expand Up @@ -148,8 +150,16 @@ class MainActivity : AppCompatActivity() {
capabilities
?.let {
(it.zoom as? Zoom.VariableZoom)
?.let { zoom -> setupZoom(zoom) }
?: run { zoomSeekBar.visibility = View.GONE }
?.let {
cameraZoom = it
focusView.scaleListener = this::scaleZoom
focusView.ptrListener = this::pointerChanged
}
?: run {
zoomLvl?.visibility = View.GONE
focusView.scaleListener = null
focusView.ptrListener = null
}

torchSwitch.visibility = if (it.flashModes.contains(Flash.Torch)) View.VISIBLE else View.GONE
}
Expand All @@ -159,19 +169,27 @@ class MainActivity : AppCompatActivity() {
switchCamera.visibility = if (fotoapparat.isAvailable(front())) View.VISIBLE else View.GONE
}

private fun setupZoom(zoom: Zoom.VariableZoom) {
zoomSeekBar.max = zoom.maxZoom
cameraZoom = zoom
zoomSeekBar.visibility = View.VISIBLE
zoomSeekBar onProgressChanged { updateZoom(zoomSeekBar.progress) }
updateZoom(0)
}
//When zooming slowly, the values are approximately 0.9 ~ 1.1
private fun scaleZoom(scaleFactor: Float) {
//convert to -0.1 ~ 0.1
val plusZoom = if (scaleFactor < 1) -1 * (1 - scaleFactor) else scaleFactor - 1
val newZoom = curZoom + plusZoom
if (newZoom < 0 || newZoom > 1) return

private fun updateZoom(progress: Int) {
fotoapparat.setZoom(progress.toFloat() / zoomSeekBar.max)
curZoom = newZoom
fotoapparat.setZoom(curZoom)
val progress = (cameraZoom.maxZoom * curZoom).roundToInt()
val value = cameraZoom.zoomRatios[progress]
val roundedValue = ((value.toFloat()) / 10).roundToInt().toFloat() / 10
zoomLvl.text = String.format("%.1f ×", roundedValue)

zoomLvl.visibility = View.VISIBLE
zoomLvl.text = String.format("%.1f×", roundedValue)
}

private fun pointerChanged(fingerCount: Int){
if(fingerCount == 0) {
zoomLvl?.visibility = View.GONE
}
}
}

Expand Down
Loading

0 comments on commit 7039cf2

Please sign in to comment.