Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to crop image rectangle in camera preview on CameraX

I have a custom camera app which has a centered rectangle view, as you can see below:

enter image description here

When I take a picture I want to ignore everything outside the rectangle. And this is my XML layout:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/black_50">

    <TextureView
        android:id="@+id/viewFinder"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <View
        android:layout_width="match_parent"
        android:layout_height="250dp"
        android:layout_margin="16dp"
        android:background="@drawable/rectangle"
        app:layout_constraintBottom_toTopOf="@+id/cameraBottomView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <View
        android:id="@+id/cameraBottomView"
        android:layout_width="match_parent"
        android:layout_height="130dp"
        android:background="@color/black_50"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <ImageButton
        android:id="@+id/cameraCaptureImageButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@android:color/transparent"
        android:src="@drawable/ic_capture_image"
        app:layout_constraintBottom_toBottomOf="@id/cameraBottomView"
        app:layout_constraintEnd_toEndOf="@id/cameraBottomView"
        app:layout_constraintStart_toStartOf="@id/cameraBottomView"
        app:layout_constraintTop_toTopOf="@id/cameraBottomView"
        tools:ignore="ContentDescription" />

</androidx.constraintlayout.widget.ConstraintLayout>

And this is my kotlin code for the cameraX preview:

class CameraFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_camera, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewFinder.post { setupCamera() }
    }

    private fun setupCamera() {
        CameraX.unbindAll()
        CameraX.bindToLifecycle(
            this,
            buildPreviewUseCase(),
            buildImageCaptureUseCase(),
            buildImageAnalysisUseCase()
        )
    }

    private fun buildPreviewUseCase(): Preview {
        val preview = Preview(
            UseCaseConfigBuilder.buildPreviewConfig(
                viewFinder.display
            )
        )
        preview.setOnPreviewOutputUpdateListener { previewOutput ->
            updateViewFinderWithPreview(previewOutput)
            correctPreviewOutputForDisplay(previewOutput.textureSize)
        }
        return preview
    }

    private fun updateViewFinderWithPreview(previewOutput: Preview.PreviewOutput) {
        val parent = viewFinder.parent as ViewGroup
        parent.removeView(viewFinder)
        parent.addView(viewFinder, 0)
        viewFinder.surfaceTexture = previewOutput.surfaceTexture
    }

    /**
     * Corrects the camera/preview's output to the display, by scaling
     * up/down and/or rotating the camera/preview's output.
     */
    private fun correctPreviewOutputForDisplay(textureSize: Size) {
        val matrix = Matrix()

        val centerX = viewFinder.width / 2f
        val centerY = viewFinder.height / 2f

        val displayRotation = getDisplayRotation()
        val (dx, dy) = getDisplayScalingFactors(textureSize)

        matrix.postRotate(displayRotation, centerX, centerY)
        matrix.preScale(dx, dy, centerX, centerY)

        // Correct preview output to account for display rotation and scaling
        viewFinder.setTransform(matrix)
    }

    private fun getDisplayRotation(): Float {
        val rotationDegrees = when (viewFinder.display.rotation) {
            Surface.ROTATION_0 -> 0
            Surface.ROTATION_90 -> 90
            Surface.ROTATION_180 -> 180
            Surface.ROTATION_270 -> 270
            else -> throw IllegalStateException("Unknown display rotation ${viewFinder.display.rotation}")
        }
        return -rotationDegrees.toFloat()
    }

    private fun getDisplayScalingFactors(textureSize: Size): Pair<Float, Float> {
        val cameraPreviewRation = textureSize.height / textureSize.width.toFloat()
        val scaledWidth: Int
        val scaledHeight: Int
        if (viewFinder.width > viewFinder.height) {
            scaledHeight = viewFinder.width
            scaledWidth = (viewFinder.width * cameraPreviewRation).toInt()
        } else {
            scaledHeight = viewFinder.height
            scaledWidth = (viewFinder.height * cameraPreviewRation).toInt()
        }
        val dx = scaledWidth / viewFinder.width.toFloat()
        val dy = scaledHeight / viewFinder.height.toFloat()
        return Pair(dx, dy)
    }

    private fun buildImageCaptureUseCase(): ImageCapture {
        val capture = ImageCapture(
            UseCaseConfigBuilder.buildImageCaptureConfig(
                viewFinder.display
            )
        )
        cameraCaptureImageButton.setOnClickListener {
            capture.takePicture(
                FileCreator.createTempFile(JPEG_FORMAT),
                Executors.newSingleThreadExecutor(),
                object : ImageCapture.OnImageSavedListener {
                    override fun onImageSaved(file: File) {
                        requireActivity().runOnUiThread {
                            launchGalleryFragment(file.absolutePath)
                        }
                    }

                    override fun onError(
                        imageCaptureError: ImageCapture.ImageCaptureError,
                        message: String,
                        cause: Throwable?
                    ) {
                        Toast.makeText(requireContext(), "Error: $message", Toast.LENGTH_LONG)
                            .show()
                        Log.e("CameraFragment", "Capture error $imageCaptureError: $message", cause)
                    }
                })
        }
        return capture
    }

    private fun buildImageAnalysisUseCase(): ImageAnalysis {
        val analysis = ImageAnalysis(
            UseCaseConfigBuilder.buildImageAnalysisConfig(
                viewFinder.display
            )
        )
        analysis.setAnalyzer(
            Executors.newSingleThreadExecutor(),
            ImageAnalysis.Analyzer { image, rotationDegrees ->
                Log.d(
                    "CameraFragment",
                    "Image analysis: $image - Rotation degrees: $rotationDegrees"
                )
            })
        return analysis
    }

    private fun launchGalleryFragment(path: String) {
        val action = CameraFragmentDirections.actionLaunchGalleryFragment(path)
        findNavController().navigate(action)
    }

}

And when I take the picture and send it into new page (GalleryPage), it's show all screen from the camera preview as you can see below:

enter image description here

And this is the kotlin code to get the picture from cameraX preview and display it into ImageView:

class GalleryFragment : Fragment() {

    private lateinit var imageView: ImageView


    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_gallery, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        imageView = view.findViewById(R.id.img)

        val imageFilePath = GalleryFragmentArgs.fromBundle(arguments!!).data
        val bitmap = BitmapFactory.decodeFile(imageFilePath)
        val rotatedBitmap = bitmap.rotate(90)

        if (imageFilePath.isBlank()) {
            Log.i(
                "GalleryFragment",
                "Image is Null or Empty"
            )
        } else {
            Glide.with(activity!!)
                .load(rotatedBitmap)
                .into(imageView)
        }

    }

    private fun Bitmap.rotate(degree:Int):Bitmap{
        // Initialize a new matrix
        val matrix = Matrix()

        // Rotate the bitmap
        matrix.postRotate(degree.toFloat())

        // Resize the bitmap
        val scaledBitmap = Bitmap.createScaledBitmap(
            this,
            width,
            height,
            true
        )

        // Create and return the rotated bitmap
        return Bitmap.createBitmap(
            scaledBitmap,
            0,
            0,
            scaledBitmap.width,
            scaledBitmap.height,
            matrix,
            true
        )
    }

}

Can somebody help me how to crop the image properly? Because I already search and research how to do it but still confused and not working for me.

like image 880
R Rifa Fauzi Komara Avatar asked Dec 09 '19 03:12

R Rifa Fauzi Komara


1 Answers

I have a solution, I just use this function to cropping the Image after capturing the Image:

private fun cropImage(bitmap: Bitmap, frame: View, reference: View): ByteArray {
        val heightOriginal = frame.height
        val widthOriginal = frame.width
        val heightFrame = reference.height
        val widthFrame = reference.width
        val leftFrame = reference.left
        val topFrame = reference.top
        val heightReal = bitmap.height
        val widthReal = bitmap.width
        val widthFinal = widthFrame * widthReal / widthOriginal
        val heightFinal = heightFrame * heightReal / heightOriginal
        val leftFinal = leftFrame * widthReal / widthOriginal
        val topFinal = topFrame * heightReal / heightOriginal
        val bitmapFinal = Bitmap.createBitmap(
            bitmap,
            leftFinal, topFinal, widthFinal, heightFinal
        )
        val stream = ByteArrayOutputStream()
        bitmapFinal.compress(
            Bitmap.CompressFormat.JPEG,
            100,
            stream
        ) //100 is the best quality possibe
        return stream.toByteArray()
    }

Crop an image taking a reference a view parent like a frame and a view child like final reference

  • param bitmap image to crop
  • param frame where the image is set it
  • param reference frame to take reference for a crop the image
  • return image already cropped

You can see this example: https://github.com/rrifafauzikomara/CustomCamera/tree/custom_camerax

like image 91
R Rifa Fauzi Komara Avatar answered Sep 23 '22 09:09

R Rifa Fauzi Komara