10

After update from

androidx.camera:camera-core:1.0.0-alpha03

to

androidx.camera:camera-core:1.0.0-alpha06

signatures of methods setTargetAspectRatio (in ImageCaptureConfig.Builder) and takePicture (in ImageCapture) have been changed.

Official documentation and info in web doesn't show how to use new methods (how to specify executor).

Code which broken after update:

...
val captureConfig = ImageCaptureConfig.Builder()
    .setTargetAspectRatioCustom(Rational(1, 1)) //this method changed
    .setFlashMode(flashMode)
    .setLensFacing(lensFacing)
    .build()

val capture = ImageCapture(captureConfig)

binding.takeAPhoto.setOnClickListener {
    ...
    val imageFile = createTempFile(System.currentTimeMillis().toString(), ".jpg")
    capture.takePicture(imageFile, object : ImageCapture.OnImageSavedListener { //this method also changed

        override fun onImageSaved(file: File) {
            ...
        }

        override fun onError(useCaseError: ImageCapture.UseCaseError, message: String, cause: Throwable?) {
            ...
        })
    }
}

Does anyone have (or know where to find) example of how to use new methods? Thanks in advance

Peter Staranchuk
  • 1,087
  • 1
  • 11
  • 24
  • 2
    Create an `Executor`, perhaps using `Executors`, then pass that `Executor` into the methods. Your listener methods will be called on a thread supplied by that `Executor`. – CommonsWare Oct 14 '19 at 11:04
  • 1
    Example: `Executors.newCachedThreadPool()` – kos Oct 21 '19 at 01:20

7 Answers7

10

The official Google Codelabs which obviously have been updated recently use: Executors.newSingleThreadExecutor()

Reference: https://codelabs.developers.google.com/codelabs/camerax-getting-started/#4

Edit: Since @kos's response also makes sense to me, I've added these two official Android docs references:

https://developer.android.com/reference/java/util/concurrent/Executors.html#newSingleThreadExecutor()

https://developer.android.com/reference/java/util/concurrent/Executors.html#newCachedThreadPool()

This way every reader of this topic can make up his/her own mind with respect to executors.

FURTHER EDIT: There are crucial API changes since 1.0.0-alpha07 so I studied some of the docs. There's a GitHub sample showing executor retrieval like so mainExecutor = ContextCompat.getMainExecutor(requireContext())(Source)

If some of you already implemented CameraX and it works fine, I'd definitely wait for the beta release as recommended by Android's release notes

  • 1
    I switched from `Executors.newSingleThreadExecutor()` to `Executors.newCachedThreadPool()` and it resolved issues on my end which I was only experiencing on a small subset of test devices. thanks – Sameer J Nov 09 '19 at 06:48
  • @SameerJ thanks for sharing your experience. I'll definitely change this in my project since I don't have many test devices! – postfixNotation Dec 05 '19 at 09:03
  • With the release of AndroidX Jetpack you can use ```AsyncTask.THREAD_POOL_EXECUTOR```. As describe in this release note of Jetpack Loader https://developer.android.com/jetpack/androidx/releases/loader?hl=it#1.1.0-alpha01 Quote: > **The default Executor for AsyncTaskLoader is now AsyncTask.THREAD_POOL_EXECUTOR rather than a custom Executor.**. I got the hint from @Gurumod 's Answer here: https://stackoverflow.com/a/58414168/7001213 – Ahwar May 26 '20 at 05:31
1

I faced same thing as you are facing. I resolved it from my side.

class MainActivity : AppCompatActivity(), Executor {
    private var right: Int = 0
    private var bottom: Int = 0
    private var left: Int = 0
    private var top: Int = 0
    private lateinit var preview: Preview
    private val REQUEST_CODE_PERMISSIONS = 10
    private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
    private lateinit var imageCapture: ImageCapture
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        if (allPermissionsGranted()) {
            viewFinder.post { startCamera() }
        } else {
            ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
        }

        viewFinder.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
            updateTransform()
        }

        buttonPlus.setOnClickListener {
            if (right < 100) {
                right += 100
                bottom += 100
                left += 100
                top += 100
                val my = Rect(left, top, right, bottom)
                preview.zoom(my)
            }
        }

        buttonMinus.setOnClickListener {
            if (right > 0) {
                right -= 100
                bottom -= 100
                left -= 100
                top -= 100
                val my = Rect(left, top, right, bottom)
                preview.zoom(my)
            }
        }
    }

    @SuppressLint("RestrictedApi")
    private fun startCamera() {
        val metrics = DisplayMetrics().also { viewFinder.display.getRealMetrics(it) }
        val screenAspectRatio = Rational(metrics.widthPixels, metrics.heightPixels)
        val previewConfig = PreviewConfig.Builder().apply {
            setTargetAspectRatioCustom(screenAspectRatio)
            setTargetRotation(viewFinder.display.rotation)
        }.build()
        preview = Preview(previewConfig)
        preview.setOnPreviewOutputUpdateListener {
            val parent = viewFinder.parent as ViewGroup
            parent.removeView(viewFinder)
            parent.addView(viewFinder, 0)
            viewFinder.surfaceTexture = it.surfaceTexture
            updateTransform()
        }
        CameraX.bindToLifecycle(this, preview)

        captureImage()
    }

    @SuppressLint("RestrictedApi")
    private fun captureImage() {
        val imageCaptureConfig = ImageCaptureConfig.Builder()
                .apply {
                    setTargetAspectRatioCustom(Rational(1, 1))
                    setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY)
                }.build()
        imageCapture = ImageCapture(imageCaptureConfig)
        CameraX.bindToLifecycle(this, imageCapture)
        capture_button.setOnClickListener {
            val file = File(this.externalMediaDirs.first(), "${System.currentTimeMillis()}.jpg")
            imageCapture.takePicture(file, this, object : ImageCapture.OnImageSavedListener {
                override fun onImageSaved(file: File) {
                    val msg = "Photo capture succeeded: ${file.absolutePath}"
                    Log.d("CameraXApp", msg)
                }

                override fun onError(imageCaptureError: ImageCapture.ImageCaptureError, message: String, cause: Throwable?) {
                    val msg = "Photo capture failed: $message"
                    Log.e("CameraXApp", msg)
                    cause?.printStackTrace()
                }
            })
        }
    }

    override fun execute(command: Runnable) {
        command.run()
    }

    private fun updateTransform() {
        val matrix = Matrix()
        val centerX = viewFinder.width / 2f
        val centerY = viewFinder.height / 2f
        val rotationDegrees = when (viewFinder.display.rotation) {
            Surface.ROTATION_0 -> 0
            Surface.ROTATION_90 -> 90
            Surface.ROTATION_180 -> 180
            Surface.ROTATION_270 -> 270
            else -> return
        }
        matrix.postRotate(-rotationDegrees.toFloat(), centerX, centerY)
        viewFinder.setTransform(matrix)
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
        if (requestCode == REQUEST_CODE_PERMISSIONS) {
            if (allPermissionsGranted()) {
                viewFinder.post { startCamera() }
            } else {
                Toast.makeText(this, "Permissions not granted by the user.", Toast.LENGTH_SHORT).show()
                finish()
            }
        }
    }

    private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
        ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
    }


    override fun onDestroy() {
        super.onDestroy()
        imageCapture.let {
            CameraX.unbind(imageCapture)
        }
    }
}

And the output is (As I print log in onImageSaved method)

Photo capture succeeded: /storage/emulated/0/Android/media/com.akshay.cameraxzoominoutdemo/1571052301192.jpg

It's working fine for me, try out this.

Akshay Raiyani
  • 839
  • 6
  • 15
1

You can do it like this.

imageCapture.takePicture(file, { it.run() }, object : ImageCapture.OnImageSavedListener {
    override fun onImageSaved(file: File) {}
    override fun onError(useCaseError: ImageCapture.ImageCaptureError, message: String, cause: Throwable?) {}
})
axierjhtjz
  • 6,647
  • 6
  • 47
  • 65
  • Which thread would that execute on? Not obvious on which thread the CameraX framework will invoke the it.run() – kos Oct 21 '19 at 01:20
0

Here is a change log for the changes in alpha06 : https://developer.android.com/jetpack/androidx/releases/camera

  • setTargetAspectRatio() method now takes AspectRatio enum with 4_3 or 16_9 value.
  • takePicture() method takes (file, metadata, executor, imageSavedListener) // could use executor as per your case/need. example is val executor = Executors.newSingleThreadExecutor()
  • instead of useCase.onPreviewOutputUpdateListener = use useCase.setOnPreviewOutputUpdateListener()

FYI : CameraX will be in Beta in Dec 2019

Himanshu Walia
  • 106
  • 1
  • 7
0

Inside your click listener call this function/method :

    private fun saveImage(){

        val file = File(this.externalMediaDirs.first(), "${System.currentTimeMillis()}.jpg")
        val fileB = ImageCapture.OutputFileOptions.Builder(file).build()

        imageCapture.takePicture(fileB, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback {
            override fun onImageSaved(fileB: ImageCapture.OutputFileResults) {
                val msg = "${fileB.savedUri} - ${file.absolutePath} - ${file.toURI()}"
            }

            override fun onError(imageCaptureError: ImageCaptureException) {
                val msg = "Photo capture failed: ${imageCaptureError.toString()}"
            }
        })
    }

And the msg in onImageSaved will contain something like this :

null - /storage/emulated/0/Android/media/com.mua.camx/1607589430984.jpg - file:/storage/emulated/0/Android/media/com.mua.camx/1607589430984.jpg
Maifee Ul Asad
  • 1,912
  • 2
  • 14
  • 40
-1

You have to only run the command as below.

@Override
public void execute(Runnable command) {
    command.run(); // <-- THIS IS NEEDED
}
Syed Fahad
  • 127
  • 9
-2

CameraX provides with built-in executors and take picture can be implemented as below:

imgCaptureButton.setOnClickListener(new View.OnClickListener() {
    @Override
    @SuppressLint("RestrictedApi")
    public void onClick(View v) {
        imgCap.takePicture(CameraXExecutors.mainThreadExecutor(),new ImageCapture.OnImageCapturedListener() {
            @Override
            public void onCaptureSuccess(ImageProxy image, int rotationDegrees) {
                super.onCaptureSuccess(image, rotationDegrees);

                // Play with the Image here.
            }
        });
    }
});

It does not use File to save the image, instead saves the image as a buffer in the memory.

  • This is not correct, the `CameraXExecutors` class is restricted to be available on the library level only. It's internal to CameraX. – kos Oct 21 '19 at 01:21