0% found this document useful (0 votes)
124 views10 pages

Camera Fragment

This document contains code for a camera fragment in an Android application. The fragment handles camera operations like preview, photo capture, and image analysis. It initializes CameraX components, sets up listeners for orientation changes and volume button presses, and saves captured photos to disk.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
124 views10 pages

Camera Fragment

This document contains code for a camera fragment in an Android application. The fragment handles camera operations like preview, photo capture, and image analysis. It initializes CameraX components, sets up listeners for orientation changes and volume button presses, and saves captured photos to disk.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 10

/*

* Copyright 2019 Google LLC


*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

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() {

private lateinit var container: ConstraintLayout


private lateinit var viewFinder: TextureView
private lateinit var outputDirectory: File
private lateinit var broadcastManager: LocalBroadcastManager

private var displayId = -1


private var lensFacing_ = CameraX.LensFacing.BACK
private var torch = false;
private var preview: Preview? = null
private var imageCapture: ImageCapture? = null
private var imageAnalyzer: ImageAnalysis? = null

/** Volume down button receiver used to trigger shutter */


private val volumeDownReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val keyCode = intent.getIntExtra(KEY_EVENT_EXTRA,
KeyEvent.KEYCODE_UNKNOWN)
when (keyCode) {
// When the volume down button is pressed, simulate a shutter
button click
KeyEvent.KEYCODE_VOLUME_DOWN -> {
val shutter = container
.findViewById<ImageButton>(R.id.camera_capture_button)
shutter.simulateClick()
}
}
}
}

/** Declare worker thread at the class level so it can be reused after config
changes */
private val analyzerThread = HandlerThread("LuminosityAnalysis").apply
{ start() }

/** Internal reference of the [DisplayManager] */


private lateinit var displayManager: DisplayManager

/**
* 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
}

override fun onCreate(savedInstanceState: Bundle?) {


super.onCreate(savedInstanceState)
// Mark this as a retain fragment, so the lifecycle does not get restarted
on config change
retainInstance = true
}

override fun onResume() {


super.onResume()
// Make sure that all permissions are still present, since user could have
removed them
// while the app was on paused state
if (!PermissionsFragment.hasPermissions(requireContext())) {
Navigation.findNavController(requireActivity(),
R.id.fragment_container).navigate(
CameraFragmentDirections.actionCameraToPermissions())

}
}

override fun onDestroyView() {


super.onDestroyView()

// Unregister the broadcast receivers and listeners


broadcastManager.unregisterReceiver(volumeDownReceiver)
displayManager.unregisterDisplayListener(displayListener)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.fragment_camera, container, false)

private fun setGalleryThumbnail(file: File) {


// Reference of the view that holds the gallery thumbnail
val thumbnail = container.findViewById<ImageButton>(R.id.photo_view_button)

// Run the operations in the view's thread


thumbnail.post {

// Remove thumbnail padding

thumbnail.setPadding(resources.getDimension(R.dimen.stroke_small).toInt())

// Load thumbnail into circular button using Glide


Glide.with(thumbnail)
.load(file)
.apply(RequestOptions.circleCropTransform())
.into(thumbnail)
}
}

/** 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()
}

override fun onImageSaved(photoFile: File) {


Log.d(TAG, "Photo capture succeeded: ${photoFile.absolutePath}")

// We can only change the foreground Drawable using API level 23+ API
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

// Update the gallery thumbnail with latest picture taken


setGalleryThumbnail(photoFile)
}

// Implicit broadcasts will be ignored for devices running API


// level >= 24, so if you only target 24+ you can remove this statement
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
requireActivity().sendBroadcast(
Intent(Camera.ACTION_NEW_PICTURE, Uri.fromFile(photoFile)))
}

// If the folder selected is an external media directory, this is


unnecessary
// but otherwise other apps will not be able to access our images
unless we
// scan them using [MediaScannerConnection]
val mimeType = MimeTypeMap.getSingleton()
.getMimeTypeFromExtension(photoFile.extension)
MediaScannerConnection.scanFile(
context, arrayOf(photoFile.absolutePath), arrayOf(mimeType),
null)
}
}

@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)

// Every time the orientation of device changes, recompute layout


displayManager = viewFinder.context
.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
displayManager.registerDisplayListener(displayListener, null)

// Determine the output directory


outputDirectory = MainActivity.getOutputDirectory(requireContext())

// Wait for the views to be properly laid out


viewFinder.post {
// Keep track of the display in which this view is attached
displayId = viewFinder.display.displayId

// Build UI controls and bind all camera use cases


updateCameraUi()
bindCameraUseCases()

// 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}")

// Set up the view finder use case to display camera preview


val viewFinderConfig = PreviewConfig.Builder().apply {
setLensFacing(lensFacing_)
// We request aspect ratio but no resolution to let CameraX optimize
our use cases
setTargetAspectRatio(screenAspectRatio)
// Set initial target rotation, we will have to call this again if
rotation changes
// during the lifecycle of this use case
setTargetRotation(viewFinder.display.rotation)
}.build()

// Use the auto-fit preview builder to automatically handle size and


orientation changes
preview = AutoFitPreviewBuilder.build(viewFinderConfig, viewFinder)

// Set up the capture use case to allow users to take photos


val imageCaptureConfig = ImageCaptureConfig.Builder().apply {
setLensFacing(lensFacing_)
setCaptureMode(CaptureMode.MIN_LATENCY)
// We request aspect ratio but no resolution to match preview config
but letting
// CameraX optimize for whatever specific resolution best fits
requested capture mode
setTargetAspectRatio(screenAspectRatio)
// Set initial target rotation, we will have to call this again if
rotation changes
// during the lifecycle of this use case
setTargetRotation(viewFinder.display.rotation)
}.build()

imageCapture = ImageCapture(imageCaptureConfig)

// Setup image analysis pipeline that computes average pixel luminance in


real time
val analyzerConfig = ImageAnalysisConfig.Builder().apply {
setLensFacing(lensFacing_)
// Use a worker thread for image analysis to prevent preview glitches
setCallbackHandler(Handler(analyzerThread.looper))
// In our analysis, we care more about the latest image than analyzing
*every* image
setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
// Set initial target rotation, we will have to call this again if
rotation changes
// during the lifecycle of this use case
setTargetRotation(viewFinder.display.rotation)
}.build()

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)}")
}
}

// Apply declared configs to CameraX using the same lifecycle owner


CameraX.bindToLifecycle(
viewLifecycleOwner, preview, imageCapture, imageAnalyzer)
}

/** Method used to re-draw the camera UI controls, called every time
configuration changes */
@SuppressLint("RestrictedApi")
private fun updateCameraUi() {

// Remove previous UI if any


container.findViewById<ConstraintLayout>(R.id.camera_ui_container)?.let {
container.removeView(it)
}

// Inflate a new view containing all UI for controlling the camera


val controls = View.inflate(requireContext(), R.layout.camera_ui_container,
container)

// Listener for button used to capture photo

controls.findViewById<ImageButton>(R.id.camera_capture_button).setOnClickListener {
// Get a stable reference of the modifiable image capture use case
imageCapture?.let { imageCapture ->

// Create output file to hold the image


val photoFile = createFile(outputDirectory, FILENAME,
PHOTO_EXTENSION)

// Setup image capture metadata


val metadata = Metadata().apply {
// Mirror image when using the front camera
isReversedHorizontal = lensFacing_ == CameraX.LensFacing.FRONT
}

// Setup image capture listener which is triggered after photo has


been taken
imageCapture.takePicture(photoFile, imageSavedListener, metadata)

// We can only change the foreground Drawable using API level 23+
API
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

// Display flash animation to indicate that photo was captured


container.postDelayed({
container.foreground = ColorDrawable(Color.WHITE)
container.postDelayed(
{ container.foreground = null },
ANIMATION_FAST_MILLIS)
}, ANIMATION_SLOW_MILLIS)
}
}
}

// Listener for button used to switch cameras

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

//tentar passar isso pro projeto escelsa

// Unbind all use cases and bind them again with the new lens
facing configuration
//CameraX.unbindAll()
//bindCameraUseCases()
} catch (exc: Exception) {
// Do nothing
}
}

// Listener for button used to view last photo

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

// Keep track of frames analyzed


frameTimestamps.push(System.currentTimeMillis())

// Compute the FPS using a moving average


while (frameTimestamps.size >= frameRateWindow)
frameTimestamps.removeLast()
framesPerSecond = 1.0 / ((frameTimestamps.peekFirst() -
frameTimestamps.peekLast()) / frameTimestamps.size.toDouble())
* 1000.0

// Calculate the average luma no more often than every second


if (frameTimestamps.first - lastAnalyzedTimestamp >=
TimeUnit.SECONDS.toMillis(1)) {
lastAnalyzedTimestamp = frameTimestamps.first

// Since format in ImageAnalysis is YUV, image.planes[0] contains


the luminance
// plane
val buffer = image.planes[0].buffer

// Extract image data from callback object


val data = buffer.toByteArray()
// Convert the data into an array of pixel values ranging 0-255
val pixels = data.map { it.toInt() and 0xFF }

// Compute average luminance for the image


val luma = pixels.average()

// Call all listeners with new value


listeners.forEach { it(luma) }
}
}
}

companion object {
private const val TAG = "CameraXBasic"
private const val FILENAME = "yyyy-MM-dd-HH-mm-ss-SSS"
private const val PHOTO_EXTENSION = ".jpg"

/** Helper function used to create a timestamped file */


private fun createFile(baseFolder: File, format: String, extension: String)
=
File(baseFolder, SimpleDateFormat(format, Locale.US)
.format(System.currentTimeMillis()) + extension)
}
}

You might also like