Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9454f3e4d1 | |||
| 7094f9850a | |||
| 7039cf2059 | |||
| 09a551635c |
+5
-9
@@ -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 : [
|
||||
@@ -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',
|
||||
@@ -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'
|
||||
@@ -57,6 +56,3 @@ task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = versions.gradle
|
||||
}
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,13 @@ enum class ScaleType {
|
||||
* The preview will be scaled so as its one dimensions will be equal and the other one equal or
|
||||
* smaller than the corresponding dimension of the view
|
||||
*/
|
||||
CenterInside
|
||||
CenterInside,
|
||||
|
||||
|
||||
/**
|
||||
* The preview will be scaled so as its one dimensions will be equal and the other one equal or
|
||||
* larger than the corresponding dimension of the view with focus on the top part
|
||||
*/
|
||||
TopCrop,
|
||||
|
||||
}
|
||||
+18
-3
@@ -23,7 +23,8 @@ internal class BitmapPhotoTransformer(
|
||||
desiredResolution = desiredResolution
|
||||
)
|
||||
|
||||
val decodedBitmap = input.decodeBitmap(scaleFactor) ?: throw UnableToDecodeBitmapException()
|
||||
val decodedBitmap = input.decodeBitmap(scaleFactor, originalResolution, desiredResolution)
|
||||
?: throw UnableToDecodeBitmapException()
|
||||
|
||||
val bitmap = if (decodedBitmap.width == desiredResolution.width && decodedBitmap.height == desiredResolution.height) {
|
||||
decodedBitmap
|
||||
@@ -44,14 +45,28 @@ internal class BitmapPhotoTransformer(
|
||||
|
||||
}
|
||||
|
||||
private fun Photo.decodeBitmap(scaleFactor: Float): Bitmap? {
|
||||
private fun Photo.decodeBitmap(
|
||||
scaleFactor: Float,
|
||||
originalResolution: Resolution,
|
||||
desiredResolution: Resolution
|
||||
): Bitmap? {
|
||||
val options = BitmapFactory.Options()
|
||||
options.inSampleSize = scaleFactor.toInt()
|
||||
options.inScaled = true
|
||||
|
||||
if (desiredResolution.width > desiredResolution.height) {
|
||||
options.inDensity = originalResolution.width
|
||||
options.inTargetDensity = desiredResolution.width * options.inSampleSize
|
||||
} else {
|
||||
options.inDensity = originalResolution.height
|
||||
options.inTargetDensity = desiredResolution.height * options.inSampleSize
|
||||
}
|
||||
|
||||
return BitmapFactory.decodeByteArray(
|
||||
encodedImage,
|
||||
0,
|
||||
encodedImage.size
|
||||
encodedImage.size,
|
||||
options
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@ private fun ViewGroup.layoutTextureView(
|
||||
) = when (scaleType) {
|
||||
ScaleType.CenterInside -> previewResolution?.centerInside(this)
|
||||
ScaleType.CenterCrop -> previewResolution?.centerCrop(this)
|
||||
ScaleType.TopCrop -> previewResolution?.topCrop(this)
|
||||
else -> null
|
||||
}
|
||||
|
||||
@@ -129,6 +130,28 @@ private fun Resolution.centerCrop(view: ViewGroup) {
|
||||
view.layoutChildrenAt(rect)
|
||||
}
|
||||
|
||||
private fun Resolution.topCrop(view: ViewGroup) {
|
||||
val scale = Math.max(
|
||||
view.measuredWidth / width.toFloat(),
|
||||
view.measuredHeight / height.toFloat()
|
||||
)
|
||||
|
||||
val width = (width * scale).toInt()
|
||||
val height = (height * scale).toInt()
|
||||
|
||||
val extraX = Math.max(0, width - view.measuredWidth)
|
||||
|
||||
val rect = Rect(
|
||||
-extraX / 2,
|
||||
0,
|
||||
width - extraX / 2,
|
||||
height
|
||||
)
|
||||
|
||||
view.layoutChildrenAt(rect)
|
||||
}
|
||||
|
||||
|
||||
private fun ViewGroup.layoutChildrenAt(rect: Rect) {
|
||||
(0 until childCount).forEach {
|
||||
getChildAt(it).layout(
|
||||
|
||||
@@ -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
|
||||
@@ -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,
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -5,23 +5,26 @@ import android.util.Log;
|
||||
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;
|
||||
@@ -29,6 +32,8 @@ 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;
|
||||
@@ -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;
|
||||
|
||||
@@ -92,6 +101,8 @@ public class ActivityJava extends AppCompatActivity {
|
||||
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) {
|
||||
@@ -105,7 +116,6 @@ public class ActivityJava extends AppCompatActivity {
|
||||
takePictureOnClick();
|
||||
switchCameraOnClick();
|
||||
toggleTorchOnSwitch();
|
||||
zoomSeekBar();
|
||||
}
|
||||
|
||||
private Fotoapparat createFotoapparat() {
|
||||
@@ -129,15 +139,66 @@ public class ActivityJava extends AppCompatActivity {
|
||||
.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() {
|
||||
@@ -180,6 +241,7 @@ public class ActivityJava extends AppCompatActivity {
|
||||
activeCameraBack ? back() : front(),
|
||||
cameraConfiguration
|
||||
);
|
||||
adjustViewsVisibility();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -223,6 +285,7 @@ public class ActivityJava extends AppCompatActivity {
|
||||
super.onStart();
|
||||
if (hasCameraPermission) {
|
||||
fotoapparat.start();
|
||||
adjustViewsVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,6 +305,7 @@ public class ActivityJava extends AppCompatActivity {
|
||||
if (permissionsDelegate.resultGranted(requestCode, permissions, grantResults)) {
|
||||
hasCameraPermission = true;
|
||||
fotoapparat.start();
|
||||
adjustViewsVisibility();
|
||||
cameraView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,12 +53,6 @@
|
||||
android:padding="20dp"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/zoomSeekBar"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/switchCamera"
|
||||
android:layout_width="wrap_content"
|
||||
@@ -74,8 +68,7 @@
|
||||
android:id="@+id/zoomLvl"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal|top"
|
||||
android:layout_marginTop="50dp"
|
||||
android:layout_gravity="center"
|
||||
android:textColor="#FFF"
|
||||
android:textSize="20sp"
|
||||
tools:text="2.4" />
|
||||
|
||||
Reference in New Issue
Block a user