Kotlin Custom Camera Preview Tutorial Example With Surfaceview

kotlin take scrollview screenshot, kotlin generate pdf from view, kotlin take screenshot, kotlin custom camera, kotlin indeterminate horizontal progressbar, kotlin horizontal recyclerview, Kotlin Volley ListView JSON, Kotlin RecyclerView Volley

Kotlin Custom Camera Preview Tutorial Example With Surfaceview Tutorial With Example.

Here, you will learn how to use Surfaceview to capture images using the Kotlin language.

We will be able to create custom camera preview so that you can add custom capture button and other UI widgets on the camera preview surface.

See the below video for more reference.

 

Step 1. Permissions in New Project

Make a new project in android studio with empty activity as the default one.

Now in your AndroidManifest.xml file, add the following lines.

 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.CAMERA"/>

We have added three permissions using above lines. Read, Write and Camera permissions.

Now go to your build.gradle(Module: app) file and write the following lines

  implementation 'com.squareup.picasso:picasso:2.4.0'
    implementation 'com.karumi:dexter:5.0.0'

First line will add the picasso library in our project.

Second one will add dexter into our app. This dexter library will give us some idea for implementation of the runtime permissions.

Step 2. Writing Runtime Permissions

In your MainActivity.kt file, write down the below lines

import android.Manifest
import android.content.Intent
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.os.StrictMode
import android.widget.Toast
import com.karumi.dexter.Dexter
import com.karumi.dexter.MultiplePermissionsReport
import com.karumi.dexter.PermissionToken
import com.karumi.dexter.listener.DexterError
import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.PermissionRequestErrorListener
import com.karumi.dexter.listener.multi.MultiplePermissionsListener

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val builder = StrictMode.VmPolicy.Builder()
        StrictMode.setVmPolicy(builder.build())

        requestReadPermissions()

    }

    private fun requestReadPermissions() {

        Dexter.withActivity(this)
            .withPermissions( Manifest.permission.READ_EXTERNAL_STORAGE,
                Manifest.permission.WRITE_EXTERNAL_STORAGE,
                Manifest.permission.CAMERA)
            .withListener(object : MultiplePermissionsListener {
                override fun onPermissionsChecked(report: MultiplePermissionsReport) {
                    // check if all permissions are granted
                    if (report.areAllPermissionsGranted()) {
                        Toast.makeText(applicationContext, "All permissions are granted by user!", Toast.LENGTH_SHORT)
                            .show()
                        val intent = Intent(this@MainActivity, CaptureActivity::class.java)
                        startActivity(intent)
                        finish()
                    }

                    // check for permanent denial of any permission
                    if (report.isAnyPermissionPermanentlyDenied) {
                        // show alert dialog navigating to Settings
                        //openSettingsDialog();
                    }
                }

                override fun onPermissionRationaleShouldBeShown(permissions: List<PermissionRequest>, token: PermissionToken) {
                    token.continuePermissionRequest()
                }
            }).withErrorListener(object : PermissionRequestErrorListener {
                override fun onError(error: DexterError) {
                    Toast.makeText(applicationContext, "Some Error! ", Toast.LENGTH_SHORT).show()
                }
            })
            .onSameThread()
            .check()
    }

}

Inside onCreate() method, there is another method called requestReadPermissions()

Below is the code snippet for requestReadPermissions()

 private fun requestReadPermissions() {

        Dexter.withActivity(this)
            .withPermissions( Manifest.permission.READ_EXTERNAL_STORAGE,
                Manifest.permission.WRITE_EXTERNAL_STORAGE,
                Manifest.permission.CAMERA)
            .withListener(object : MultiplePermissionsListener {
                override fun onPermissionsChecked(report: MultiplePermissionsReport) {
                    // check if all permissions are granted
                    if (report.areAllPermissionsGranted()) {
                        Toast.makeText(applicationContext, "All permissions are granted by user!", Toast.LENGTH_SHORT)
                            .show()
                        val intent = Intent(this@MainActivity, CaptureActivity::class.java)
                        startActivity(intent)
                        finish()
                    }

                    // check for permanent denial of any permission
                    if (report.isAnyPermissionPermanentlyDenied) {
                        // show alert dialog navigating to Settings
                        //openSettingsDialog();
                    }
                }

                override fun onPermissionRationaleShouldBeShown(permissions: List<PermissionRequest>, token: PermissionToken) {
                    token.continuePermissionRequest()
                }
            }).withErrorListener(object : PermissionRequestErrorListener {
                override fun onError(error: DexterError) {
                    Toast.makeText(applicationContext, "Some Error! ", Toast.LENGTH_SHORT).show()
                }
            })
            .onSameThread()
            .check()
    }

We are covering three types of permissions in the above method. One is Read external storage, second is write external storage and last is camera.

This method simplifies the process of runtime permissions.

You just need to add the permissions and rest will be taken cared by this method.

When the user grants all the permissions, system will open the CaptureActivity.

Step 3. Making Camera Preview

Create a new Kotlin class and give it a name like CameraPreview.kt

Write down the below lines in CameraPreview.kt

import java.io.IOException
import android.content.Context
import android.hardware.Camera
import android.util.Log
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.View

class CameraPreview(context: Context, private var mCamera: Camera?) : SurfaceView(context), SurfaceHolder.Callback {
    private val mHolder: SurfaceHolder

    init {
        mHolder = holder
        mHolder.addCallback(this)
        // deprecated setting, but required on Android versions prior to 3.0
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)
    }

    override fun surfaceCreated(holder: SurfaceHolder) {
        try {
            // create the surface and start camera preview
            if (mCamera == null) {
                mCamera!!.setPreviewDisplay(holder)
                mCamera!!.startPreview()
            }
        } catch (e: IOException) {
            Log.d(View.VIEW_LOG_TAG, "Error setting camera preview: " + e.message)
        }

    }

    fun refreshCamera(camera: Camera?) {
        if (mHolder.surface == null) {
            // preview surface does not exist
            return
        }
        // stop preview before making changes
        try {
            mCamera!!.stopPreview()
        } catch (e: Exception) {
            // ignore: tried to stop a non-existent preview
        }

        // set preview size and make any resize, rotate or
        // reformatting changes here
        // start preview with new settings
        setCamera(camera)
        try {
            mCamera!!.setPreviewDisplay(mHolder)
            mCamera!!.startPreview()
        } catch (e: Exception) {
            Log.d(View.VIEW_LOG_TAG, "Error starting camera preview: " + e.message)
        }

    }

    override fun surfaceChanged(holder: SurfaceHolder, format: Int, w: Int, h: Int) {
        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.
        refreshCamera(mCamera)
    }

    fun setCamera(camera: Camera?) {
        //method to set a camera instance
        mCamera = camera
    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {
        // TODO Auto-generated method stub
        // mCamera.release();

    }
}

This class will help us to generate the camera preview. It will create the objects of Camera and Surface classes.

Both these objects will work together to make smooth camera preview.

Step 4. Writing Capture Activity

Make a new activity named as CaptureActivity. You need to add the following lines in activity_capture.xml file.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
                tools:context=".MainActivity">

    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal"
            android:id="@+id/cPreview">

    </LinearLayout>
    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="bottom|center"
            android:orientation="horizontal">

        <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/btnCam"
                android:textSize="15sp"
                android:text="Take Photo"/>

        <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/btnSwitch"
                android:layout_marginLeft="0dp"
                android:textSize="15sp"
                android:text="Switch Camera"/>

    </LinearLayout>

</RelativeLayout>

Now see the above code. There are two Linear Layouts inside the Relative Layout.

First linear layout will work as the preview surface. We will initiate the camera preview in this linear layout.

Second linear layout have two buttons. They are besides each other. One button is “TAKE PHOTO” and another is “SWITCH CAMERA

Now in your CaptureActivity.kt class, you should add the below source snippet

import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.hardware.Camera
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.os.StrictMode
import android.util.Log
import android.view.WindowManager
import android.widget.Button
import android.widget.LinearLayout

class CaptureActivity : AppCompatActivity() {

    private var mCamera: Camera? = null
    private var mPreview: CameraPreview? = null
    private var mPicture: Camera.PictureCallback? = null
    private var capture: Button? = null
    private var switchCamera: Button? = null
    private var myContext: Context? = null
    private var cameraPreview: LinearLayout? = null
    private var cameraFront = false

    private val pictureCallback: Camera.PictureCallback
        get() = object : Camera.PictureCallback {
            override fun onPictureTaken(data: ByteArray, camera: Camera) {
                bitmap = BitmapFactory.decodeByteArray(data, 0, data.size)
                val intent = Intent(this@CaptureActivity, PictureActivity::class.java)
                startActivity(intent)
            }
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_capture)

        val builder = StrictMode.VmPolicy.Builder()
        StrictMode.setVmPolicy(builder.build())

        window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
        myContext = this

        initCamere()

    }

    private fun initCamere(){

        mCamera = Camera.open()
        mCamera!!.setDisplayOrientation(90)
        cameraPreview = findViewById(R.id.cPreview) as LinearLayout
        mPreview = CameraPreview(myContext as CaptureActivity, mCamera)
        cameraPreview!!.addView(mPreview)

        capture = findViewById(R.id.btnCam) as Button
        capture!!.setOnClickListener { mCamera!!.takePicture(null, null, mPicture) }

        switchCamera = findViewById(R.id.btnSwitch) as Button
        switchCamera!!.setOnClickListener {
            //get the number of cameras
            val camerasNumber = Camera.getNumberOfCameras()
            if (camerasNumber > 1) {
                //release the old camera instance
                //switch camera, from the front and the back and vice versa

                releaseCamera()
                chooseCamera()
            } else {

            }
        }

        mCamera!!.startPreview()

    }

    private fun findFrontFacingCamera(): Int {

        var cameraId = -1
        // Search for the front facing camera
        val numberOfCameras = Camera.getNumberOfCameras()
        for (i in 0 until numberOfCameras) {
            val info = Camera.CameraInfo()
            Camera.getCameraInfo(i, info)
            if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                cameraId = i
                cameraFront = true
                break
            }
        }
        return cameraId

    }

    private fun findBackFacingCamera(): Int {
        var cameraId = -1
        //Search for the back facing camera
        //get the number of cameras
        val numberOfCameras = Camera.getNumberOfCameras()
        //for every camera check
        for (i in 0 until numberOfCameras) {
            val info = Camera.CameraInfo()
            Camera.getCameraInfo(i, info)
            if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                cameraId = i
                cameraFront = false
                break

            }

        }
        return cameraId
    }

    public override fun onResume() {

        super.onResume()
        if (mCamera == null) {
            mCamera = Camera.open()
            mCamera!!.setDisplayOrientation(90)
            mPicture = pictureCallback
            mPreview!!.refreshCamera(mCamera)
            Log.d("noooo", "null")
        } else {
            Log.d("noooo", "no null")
        }

    }

    fun chooseCamera() {
        //if the camera preview is the front
        if (cameraFront) {
            val cameraId = findBackFacingCamera()
            if (cameraId >= 0) {
                //open the backFacingCamera
                //set a picture callback
                //refresh the preview

                mCamera = Camera.open(cameraId)
                mCamera!!.setDisplayOrientation(90)
                mPicture = pictureCallback
                mPreview!!.refreshCamera(mCamera)
            }
        } else {
            val cameraId = findFrontFacingCamera()
            if (cameraId >= 0) {
                //open the backFacingCamera
                //set a picture callback
                //refresh the preview
                mCamera = Camera.open(cameraId)
                mCamera!!.setDisplayOrientation(90)
                mPicture = pictureCallback
                mPreview!!.refreshCamera(mCamera)
            }
        }
    }

    override fun onPause() {
        super.onPause()
        //when on Pause, release camera in order to be used from other applications
        releaseCamera()
    }

    private fun releaseCamera() {
        // stop and release camera
        if (mCamera != null) {
            mCamera!!.stopPreview()
            mCamera!!.setPreviewCallback(null)
            mCamera!!.release()
            mCamera = null
        }
    }

    companion object {
        lateinit var bitmap: Bitmap
    }

}

Deeper Explanation

First of all, see the following

private var mCamera: Camera? = null
    private var mPreview: CameraPreview? = null
    private var mPicture: Camera.PictureCallback? = null
    private var capture: Button? = null
    private var switchCamera: Button? = null
    private var myContext: Context? = null
    private var cameraPreview: LinearLayout? = null
    private var cameraFront = false

System will create the objects of various classes like Camera, CameraPreview, Button, Context etc.

An object of LinearLayout class is also there. A boolean variable is there whose name is cameraFront.

Now read the below lines

private val pictureCallback: Camera.PictureCallback
        get() = object : Camera.PictureCallback {
            override fun onPictureTaken(data: ByteArray, camera: Camera) {
                bitmap = BitmapFactory.decodeByteArray(data, 0, data.size)
                val intent = Intent(this@CaptureActivity, PictureActivity::class.java)
                startActivity(intent)
            }
        }

Compiler will execute the above code snippet when the user have clicked the image.

This coding lines will create the bitmap of the image data using BitmapFactory.

Then it will start the PictureActivity. We will create this PictureActivity in the next step.

Now see the below code lines

 val builder = StrictMode.VmPolicy.Builder()
        StrictMode.setVmPolicy(builder.build())

        window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)

These lines will help us to keep preview screen smooth and remove all other widgets from the screen.

Now focus on the following

  private fun initCamere(){

        mCamera = Camera.open()
        mCamera!!.setDisplayOrientation(90)
        cameraPreview = findViewById(R.id.cPreview) as LinearLayout
        mPreview = CameraPreview(myContext as CaptureActivity, mCamera)
        cameraPreview!!.addView(mPreview)

        capture = findViewById(R.id.btnCam) as Button
        capture!!.setOnClickListener { mCamera!!.takePicture(null, null, mPicture) }

        switchCamera = findViewById(R.id.btnSwitch) as Button
        switchCamera!!.setOnClickListener {
            //get the number of cameras
            val camerasNumber = Camera.getNumberOfCameras()
            if (camerasNumber > 1) {
                //release the old camera instance
                //switch camera, from the front and the back and vice versa

                releaseCamera()
                chooseCamera()
            } else {

            }
        }

        mCamera!!.startPreview()

    }

This method will initiate the process of creating a camera preview using surface view.

Another thing in these lines are the code for button click. When the user clicks on “TAKE PHOTO” button, compiler will execute the takePicture() method.

This method will simply capture the image.

Another button is “SWITCH CAMERA’. When user clicks this button, compiler will run the code for switching the camera between front and reverse.

For this, it will call the chooseCamera() method. Below is the code lines for chooseCamera() method.

fun chooseCamera() {
        //if the camera preview is the front
        if (cameraFront) {
            val cameraId = findBackFacingCamera()
            if (cameraId >= 0) {
                //open the backFacingCamera
                //set a picture callback
                //refresh the preview

                mCamera = Camera.open(cameraId)
                mCamera!!.setDisplayOrientation(90)
                mPicture = pictureCallback
                mPreview!!.refreshCamera(mCamera)
            }
        } else {
            val cameraId = findFrontFacingCamera()
            if (cameraId >= 0) {
                //open the backFacingCamera
                //set a picture callback
                //refresh the preview
                mCamera = Camera.open(cameraId)
                mCamera!!.setDisplayOrientation(90)
                mPicture = pictureCallback
                mPreview!!.refreshCamera(mCamera)
            }
        }
    }

This method has one if condition. This condition will be true if current camera is front camera. Otherwise compiler will go into the else statement.

Step 5. Displaying the Image

Now it is time to display the captured image. For this, make a new activity and give it a name like PictureActivity.

You need to write the below in the activity_picture.xml file.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".PictureActivity">

    <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="30sp"
            android:text="You have just taken below picture"
            tools:ignore="MissingConstraints"/>

    <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/img"
            android:padding="20dp"
            android:src="@mipmap/ic_launcher"/>

</android.support.constraint.ConstraintLayout>

This file contains one image view and one text view. Text view will not change it’s value.

We will preview the image into the image view.

Now your PictureActivity.kt file should have the below code snippet.

import android.graphics.Bitmap
import android.media.MediaScannerConnection
import android.os.Environment
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.ImageView
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.util.Calendar

class PictureActivity : AppCompatActivity() {

    private var imageView: ImageView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_picture)

        imageView = findViewById(R.id.img)

        imageView!!.setImageBitmap(CaptureActivity.bitmap)
        saveImage(CaptureActivity.bitmap)
    }

    fun saveImage(myBitmap: Bitmap): String {
        val bytes = ByteArrayOutputStream()
        myBitmap.compress(Bitmap.CompressFormat.JPEG, 90, bytes)
        val wallpaperDirectory = File(
            Environment.getExternalStorageDirectory().toString() + IMAGE_DIRECTORY
        )
        // have the object build the directory structure, if needed.

        if (!wallpaperDirectory.exists()) {
            Log.d("dirrrrrr", "" + wallpaperDirectory.mkdirs())
            wallpaperDirectory.mkdirs()
        }

        try {
            val f = File(
                wallpaperDirectory, Calendar.getInstance()
                    .timeInMillis.toString() + ".jpg"
            )
            f.createNewFile()   //give read write permission
            val fo = FileOutputStream(f)
            fo.write(bytes.toByteArray())
            MediaScannerConnection.scanFile(
                this,
                arrayOf(f.path),
                arrayOf("image/jpeg"), null
            )
            fo.close()
            Log.d("TAG", "File Saved::--->" + f.absolutePath)

            return f.absolutePath
        } catch (e1: IOException) {
            e1.printStackTrace()
        }

        return ""

    }

    companion object {
        private val IMAGE_DIRECTORY = "/CustomImage"
    }
}

First of all, compiler will just show the image into the image view.It will use bitmap to preview the image.

Then it will save the image using saveImage() method.

Download Code of Kotlin Custom Camera

https://github.com/demonuts/Kotlin-Custom-Camera-Preview-Tutorial-Example-With-Surfaceview