user19697344
user19697344

Reputation:

How to flip camera with imagebutton in kotlin

Hey there I want to flip my camera with an image button (e.g. btn_switch_camera) from back to front and vice-versa, help me!, this beautiful code has all basic things but no camera flip option so I want a flip camera imagebutton in this camera app , it has latest CameraX api it supports gradle 7.0.0 +. I just need some code to switch camera , when I did it on my own it gave me null error on image preview , so I deleted that mess and give you this original code (it must include front_camera_ icon switch to rear_camera_icon effect):

private val cameraExecutor = Executors.newSingleThreadExecutor()

private var imagePreview: Preview? = null

private var imageAnalysis: ImageAnalysis? = null

private var imageCapture: ImageCapture? = null

private var videoCapture: VideoCapture? = null

private lateinit var outputDirectory: File

private var cameraControl: CameraControl? = null

private var cameraInfo: CameraInfo? = null

private var linearZoom = 0f

private var recording = false

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = MainActivityBinding.inflate(layoutInflater)
    setContentView(binding.root)

    if (allPermissionsGranted()) {
        startCamera()
    } else {
        requestPermissions(
            REQUIRED_PERMISSIONS,
            REQUEST_CODE_PERMISSIONS
        )
    }

    outputDirectory = getOutputDirectory()

    binding.cameraCaptureButton.setOnClickListener {
        takePicture()
    }
    initCameraModeSelector()
    binding.cameraTorchButton.setOnClickListener {
        toggleTorch()
    }
}

override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<out String>,
    grantResults: IntArray
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    if (requestCode == REQUEST_CODE_PERMISSIONS) {
        if (allPermissionsGranted()) {
            startCamera()
        } else {
            finish()
        }
    }
}

/**
 * Check if all permission specified in the manifest have been granted
 */
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
    ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
}

private fun startCamera() {
    val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
    val cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
    cameraProviderFuture.addListener({
        imagePreview = Preview.Builder().apply {
            setTargetAspectRatio(AspectRatio.RATIO_16_9)
            setTargetRotation(binding.previewView.display.rotation)
        }.build()

        imageAnalysis = ImageAnalysis.Builder().apply {
            setImageQueueDepth(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
        }.build()
        imageAnalysis?.setAnalyzer(cameraExecutor, LuminosityAnalyzer())

        imageCapture = ImageCapture.Builder().apply {
            setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
            setFlashMode(ImageCapture.FLASH_MODE_AUTO)
        }.build()

        videoCapture = VideoCapture.Builder().apply {
            setTargetAspectRatio(AspectRatio.RATIO_16_9)
        }.build()

        val cameraProvider = cameraProviderFuture.get()
        val camera = cameraProvider.bindToLifecycle(
            this,
            cameraSelector,
            imagePreview,
            // imageAnalysis,
            imageCapture,
            videoCapture
        )
        binding.previewView.implementationMode = PreviewView.ImplementationMode.COMPATIBLE
        imagePreview?.setSurfaceProvider(binding.previewView.surfaceProvider)
        cameraControl = camera.cameraControl
        cameraInfo = camera.cameraInfo
        setTorchStateObserver()
        setZoomStateObserver()
    }, ContextCompat.getMainExecutor(this))
}

private fun setTorchStateObserver() {
    cameraInfo?.torchState?.observe(this, { state ->
        if (state == TorchState.ON) {
            binding.cameraTorchButton.setImageDrawable(
                ContextCompat.getDrawable(
                    this,
                    R.drawable.ic_flashlight_off_24dp
                )
            )
        } else {
            binding.cameraTorchButton.setImageDrawable(
                ContextCompat.getDrawable(
                    this,
                    R.drawable.ic_flashlight_on_24dp
                )
            )
        }
    })
}

private fun setZoomStateObserver() {
    cameraInfo?.zoomState?.observe(this, { state ->
        // state.linearZoom
        // state.zoomRatio
        // state.maxZoomRatio
        // state.minZoomRatio
        Log.d(TAG, "${state.linearZoom}")
    })
}

private fun initCameraModeSelector() {
    binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
        override fun onTabReselected(tab: TabLayout.Tab?) {}

        override fun onTabUnselected(tab: TabLayout.Tab?) {}

        override fun onTabSelected(tab: TabLayout.Tab?) {
            when (tab?.position) {
                PHOTO -> {
                    binding.cameraCaptureButton.setOnClickListener {
                        takePicture()
                    }
                }
                VIDEO -> {
                    binding.cameraCaptureButton.setOnClickListener {
                        if (recording) {
                            videoCapture?.stopRecording()
                            it.isSelected = false
                            recording = false
                        } else {
                            recordVideo()
                            it.isSelected = true
                            recording = true
                        }
                    }
                }
            }
        }

    })
}

private fun takePicture() {
    val file = createFile(
        outputDirectory,
        FILENAME,
        PHOTO_EXTENSION
    )
    val outputFileOptions = ImageCapture.OutputFileOptions.Builder(file).build()
    imageCapture?.takePicture(outputFileOptions, cameraExecutor, object : ImageCapture.OnImageSavedCallback {
        override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
            val msg = "Photo capture succeeded: ${file.absolutePath}"
            binding.previewView.post {
                Toast.makeText(this@MainActivity, msg, Toast.LENGTH_LONG).show()
            }
        }

        override fun onError(exception: ImageCaptureException) {
            val msg = "Photo capture failed: ${exception.message}"
            binding.previewView.post {
                Toast.makeText(this@MainActivity, msg, Toast.LENGTH_LONG).show()
            }
        }
    })
}

private fun recordVideo() {
    val file = createFile(
        outputDirectory,
        FILENAME,
        VIDEO_EXTENSION
    )
    val outputFileOptions = VideoCapture.OutputFileOptions.Builder(file).build()
    videoCapture?.startRecording(outputFileOptions, cameraExecutor, object : VideoCapture.OnVideoSavedCallback {
        override fun onVideoSaved(outputFileResults: VideoCapture.OutputFileResults) {
            val msg = "Video capture succeeded: ${file.absolutePath}"
            binding.previewView.post {
                Toast.makeText(this@MainActivity, msg, Toast.LENGTH_LONG).show()
            }
        }

        override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) {
            val msg = "Video capture failed: $message"
            binding.previewView.post {
                Toast.makeText(this@MainActivity, msg, Toast.LENGTH_LONG).show()
            }
        }
    })
}

private fun toggleTorch() {
    if (cameraInfo?.torchState?.value == TorchState.ON) {
        cameraControl?.enableTorch(false)
    } else {
        cameraControl?.enableTorch(true)
    }
}

// Manage camera Zoom
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
    return when (keyCode) {
        KeyEvent.KEYCODE_VOLUME_UP -> {
            if (linearZoom <= 0.9) {
                linearZoom += 0.1f
            }
            cameraControl?.setLinearZoom(linearZoom)
            true
        }
        KeyEvent.KEYCODE_VOLUME_DOWN -> {
            if (linearZoom >= 0.1) {
                linearZoom -= 0.1f
            }
            cameraControl?.setLinearZoom(linearZoom)
            true
        }
        else -> super.onKeyDown(keyCode, event)
    }
}

private class LuminosityAnalyzer : ImageAnalysis.Analyzer {
    private var lastAnalyzedTimestamp = 0L

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

    override fun analyze(image: ImageProxy) {
        image.imageInfo.rotationDegrees
        val currentTimestamp = System.currentTimeMillis()
        // Calculate the average luma no more often than every second
        if (currentTimestamp - lastAnalyzedTimestamp >=
            TimeUnit.SECONDS.toMillis(1)
        ) {
            // Since format in ImageAnalysis is YUV, image.planes[0]
            // contains the Y (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
            val pixels = data.map { it.toInt() and 0xFF }
            // Compute average luminance for the image
            val luma = pixels.average()
            // Log the new luma value
            Log.d("CameraXApp", "Average luminosity: $luma")
            // Update timestamp of last analyzed frame
            lastAnalyzedTimestamp = currentTimestamp
        }
        image.close()
    }
}

override fun onDestroy() {
    super.onDestroy()
    cameraExecutor.shutdown()
}

private fun getOutputDirectory(): File {
    // TODO: 29/01/2021 Remove externalMediaDirs (deprecated)
    val mediaDir = externalMediaDirs.firstOrNull()?.let {
        File(it, resources.getString(R.string.app_name)).apply { mkdirs() }
    }
    return if (mediaDir != null && mediaDir.exists())
        mediaDir else filesDir
}

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

    private const val REQUEST_CODE_PERMISSIONS = 10
    private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)

    private const val PHOTO = 0
    private const val VIDEO = 1

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

} Footer © 2022 GitHub, Inc. Footer navigation Terms Privacy Security Status Docs Contact GitHub Pricing API Training Blog About

Upvotes: 1

Views: 684

Answers (1)

Desmond
Desmond

Reputation: 203

You can use cameraX Library.
In CameraSelector you can use requireLensFacing(int lensFacing) to flip the camera.

Here is the code that copied from here:

private var lensFacing = CameraX.LensFacing.BACK
private var imageCapture: ImageCapture? = null

@SuppressLint("RestrictedApi")
private fun startCamera() {
    bindCameraUseCases()

    // Listener for button used to switch cameras
    switchButton = view.findViewById(R.id.switch_button)
    switchButton.setOnClickListener {
        lensFacing = if (CameraX.LensFacing.FRONT == lensFacing) {
            CameraX.LensFacing.BACK
        } else {
            CameraX.LensFacing.FRONT
        }
        try {
            // Only bind use cases if we can query a camera with this orientation
            CameraX.getCameraWithLensFacing(lensFacing)
            bindCameraUseCases()
        } catch (exc: Exception) {
            // Do nothing
        }
    }
}

private fun bindCameraUseCases() {
    // Make sure that there are no other use cases bound to CameraX
    CameraX.unbindAll()

    val previewConfig = PreviewConfig.Builder().apply {
        setLensFacing(lensFacing)
    }.build()
    val preview = Preview(previewConfig)

    val imageCaptureConfig = ImageCaptureConfig.Builder().apply {
        setLensFacing(lensFacing)
    }.build()
    imageCapture = ImageCapture(imageCaptureConfig)

    // Apply declared configs to CameraX using the same lifecycle owner
    CameraX.bindToLifecycle(this, preview, imageCapture)
}

Upvotes: 1

Related Questions