Camera Fragment
Camera Fragment
package com.android.example.cameraxbasic.fragments
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.hardware.Camera
import android.hardware.display.DisplayManager
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.HandlerThread
import android.util.DisplayMetrics
import android.util.Log
import android.util.Rational
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.TextureView
import android.view.View
import android.view.ViewGroup
import android.webkit.MimeTypeMap
import android.widget.ImageButton
import androidx.camera.core.CameraX
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageAnalysisConfig
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCapture.CaptureMode
import androidx.camera.core.ImageCapture.Metadata
import androidx.camera.core.ImageCaptureConfig
import androidx.camera.core.ImageProxy
import androidx.camera.core.Preview
import androidx.camera.core.PreviewConfig
import androidx.navigation.Navigation
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.setPadding
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.android.example.cameraxbasic.KEY_EVENT_ACTION
import com.android.example.cameraxbasic.KEY_EVENT_EXTRA
import com.android.example.cameraxbasic.MainActivity
import com.android.example.cameraxbasic.R
import com.android.example.cameraxbasic.utils.ANIMATION_FAST_MILLIS
import com.android.example.cameraxbasic.utils.ANIMATION_SLOW_MILLIS
import com.android.example.cameraxbasic.utils.AutoFitPreviewBuilder
import com.android.example.cameraxbasic.utils.simulateClick
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import kotlinx.android.synthetic.main.fragment_gallery.view.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
import java.lang.Exception
import java.nio.ByteBuffer
import java.text.SimpleDateFormat
import java.util.ArrayDeque
import java.util.Locale
import java.util.concurrent.TimeUnit
/** Helper type alias used for analysis use case callbacks */
typealias LumaListener = (luma: Double) -> Unit
/**
* Main fragment for this app. Implements all camera operations including:
* - Viewfinder
* - Photo taking
* - Image analysis
*/
class CameraFragment : Fragment() {
/** Declare worker thread at the class level so it can be reused after config
changes */
private val analyzerThread = HandlerThread("LuminosityAnalysis").apply
{ start() }
/**
* We need a display listener for orientation changes that do not trigger a
configuration
* change, for example if we choose to override config change in manifest or
for 180-degree
* orientation changes.
*/
private val displayListener = object : DisplayManager.DisplayListener {
override fun onDisplayAdded(displayId: Int) = Unit
override fun onDisplayRemoved(displayId: Int) = Unit
override fun onDisplayChanged(displayId: Int) = view?.let { view ->
if (displayId == [email protected]) {
Log.d(TAG, "Rotation changed: ${view.display.rotation}")
preview?.setTargetRotation(view.display.rotation)
preview?.enableTorch(torch)
imageCapture?.setTargetRotation(view.display.rotation)
imageAnalyzer?.setTargetRotation(view.display.rotation)
}
} ?: Unit
}
}
}
thumbnail.setPadding(resources.getDimension(R.dimen.stroke_small).toInt())
/** Define callback that will be triggered after a photo has been taken and
saved to disk */
private val imageSavedListener = object : ImageCapture.OnImageSavedListener {
override fun onError(
error: ImageCapture.UseCaseError, message: String, exc: Throwable?)
{
Log.e(TAG, "Photo capture failed: $message")
exc?.printStackTrace()
}
// We can only change the foreground Drawable using API level 23+ API
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@SuppressLint("MissingPermission")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
container = view as ConstraintLayout
viewFinder = container.findViewById(R.id.view_finder)
broadcastManager = LocalBroadcastManager.getInstance(view.context)
// Set up the intent filter that will receive events from our main activity
val filter = IntentFilter().apply { addAction(KEY_EVENT_ACTION) }
broadcastManager.registerReceiver(volumeDownReceiver, filter)
// In the background, load latest photo taken (if any) for gallery
thumbnail
lifecycleScope.launch(Dispatchers.IO) {
outputDirectory.listFiles { file ->
EXTENSION_WHITELIST.contains(file.extension.toUpperCase())
}.sorted().reversed().firstOrNull()?.let
{ setGalleryThumbnail(it) }
}
}
}
/** Declare and bind preview, capture and analysis use cases */
private fun bindCameraUseCases() {
// Get screen metrics used to setup camera for full screen resolution
val metrics = DisplayMetrics().also { viewFinder.display.getRealMetrics(it)
}
val screenAspectRatio = Rational(metrics.widthPixels, metrics.heightPixels)
Log.d(TAG, "Screen metrics: ${metrics.widthPixels} x $
{metrics.heightPixels}")
imageCapture = ImageCapture(imageCaptureConfig)
imageAnalyzer = ImageAnalysis(analyzerConfig).apply {
analyzer = LuminosityAnalyzer { luma ->
// Values returned from our analyzer are passed to the attached
listener
// We log image analysis results here -- you should do something
useful instead!
val fps = (analyzer as LuminosityAnalyzer).framesPerSecond
Log.d(TAG, "Average luminosity: $luma. " +
"Frames per second: ${"%.01f".format(fps)}")
}
}
/** Method used to re-draw the camera UI controls, called every time
configuration changes */
@SuppressLint("RestrictedApi")
private fun updateCameraUi() {
controls.findViewById<ImageButton>(R.id.camera_capture_button).setOnClickListener {
// Get a stable reference of the modifiable image capture use case
imageCapture?.let { imageCapture ->
// We can only change the foreground Drawable using API level 23+
API
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
controls.findViewById<ImageButton>(R.id.camera_switch_button).setOnClickListener {
/*
lensFacing = if (CameraX.LensFacing.FRONT == lensFacing) {
CameraX.LensFacing.BACK
} else {
CameraX.LensFacing.FRONT
}
*/
torch = !torch;
try {
// Only bind use cases if we can query a camera with this
orientation
//CameraX.getCameraWithLensFacing(lensFacing_)
preview?.enableTorch(torch);
//show..funcionou
// Unbind all use cases and bind them again with the new lens
facing configuration
//CameraX.unbindAll()
//bindCameraUseCases()
} catch (exc: Exception) {
// Do nothing
}
}
controls.findViewById<ImageButton>(R.id.photo_view_button).setOnClickListener {
Navigation.findNavController(requireActivity(),
R.id.fragment_container).navigate(
CameraFragmentDirections.actionCameraToGallery(outputDirectory.absolutePath))
}
}
/**
* Our custom image analysis class.
*
* <p>All we need to do is override the function `analyze` with our desired
operations. Here,
* we compute the average luminosity of the image by looking at the Y plane of
the YUV frame.
*/
private class LuminosityAnalyzer(listener: LumaListener? = null) :
ImageAnalysis.Analyzer {
private val frameRateWindow = 8
private val frameTimestamps = ArrayDeque<Long>(5)
private val listeners = ArrayList<LumaListener>().apply { listener?.let
{ add(it) } }
private var lastAnalyzedTimestamp = 0L
var framesPerSecond: Double = -1.0
private set
/**
* Used to add listeners that will be called with each luma computed
*/
fun onFrameAnalyzed(listener: LumaListener) = listeners.add(listener)
/**
* Helper extension function used to extract a byte array from an image
plane buffer
*/
private fun ByteBuffer.toByteArray(): ByteArray {
rewind() // Rewind the buffer to zero
val data = ByteArray(remaining())
get(data) // Copy the buffer into a byte array
return data // Return the byte array
}
/**
* Analyzes an image to produce a result.
*
* <p>The caller is responsible for ensuring this analysis method can be
executed quickly
* enough to prevent stalls in the image acquisition pipeline. Otherwise,
newly available
* images will not be acquired and analyzed.
*
* <p>The image passed to this method becomes invalid after this method
returns. The caller
* should not store external references to this image, as these references
will become
* invalid.
*
* @param image image being analyzed VERY IMPORTANT: do not close the
image, it will be
* automatically closed after this method returns
* @return the image analysis result
*/
override fun analyze(image: ImageProxy, rotationDegrees: Int) {
// If there are no listeners attached, we don't need to perform
analysis
if (listeners.isEmpty()) return
companion object {
private const val TAG = "CameraXBasic"
private const val FILENAME = "yyyy-MM-dd-HH-mm-ss-SSS"
private const val PHOTO_EXTENSION = ".jpg"