3

Disclaimer: I'm aware of the existence of this question, but it currently stands unresolved and I'm trying to provide extra information without polluting that one with useless answers that won't solve the problem anyway.

I have a custom device with a front camera that is mirrored by default, so I want to display the preview normally and I need to horizontally flip the content of PreviewView, but I'm stuck. Other people in the past have suggested using PreviewView#setScaleX(-1) but it either doesn't work at all or it needs to be called at a very specific point in the code, which I haven't found yet.

The code below is a simplified version of CameraFragment.kt in the official CameraXBasic example; I've added comments where I've already tired calling viewFinder.scaleX = -1f with no success. Honestly I don't really think that the place makes a difference because if I call it with any value other than 1 it works fine with both scaleX and scaleY, but it always ignores the negative sign so it never flips.

private lateinit var viewFinder: PreviewView

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    ...
    viewFinder = view.findViewById(R.id.view_finder)
    // HERE
    viewFinder.post {
        // HERE
        setupCamera()
    }
}

private fun setupCamera() {
    val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
    cameraProviderFuture.addListener(Runnable {
        val cameraProvider = cameraProviderFuture.get()

        val cameraSelector = CameraSelector.Builder()
            .requireLensFacing(CameraSelector.LENS_FACING_FRONT)
            .build()

       val preview = Preview.Builder()
            .build()
            .also {
                // HERE
                it.setSurfaceProvider(viewFinder.surfaceProvider)
            }

       cameraProvider.unbindAll()

        try {
            cameraProvider.bindToLifecycle(this, cameraSelector, preview)
            // HERE
        } catch (exc: Exception) {
            Log.e(TAG, "Use case binding failed", exc)
        }
        // HERE
    }, ContextCompat.getMainExecutor(requireContext()))
    // HERE
}
rdxdkr
  • 839
  • 1
  • 12
  • 22

1 Answers1

7

Depending on the implementation type (COMPATIBLE vs PERFORMANCE) and the device, PreviewView can either use a TextureView or a SurfaceView, in your case I'm assuming PreviewView is using a SurfaceView, you can confirm this by getting access to PreviewView's first child view (View.getChildAt(0)).

TextureView is just like any other View, which is why when PreviewView uses it, setting its scaleX to -1 should mirror the displayed preview. You can call PreviewView.setScaleX(-1F) once the layout is created (e.g. In onViewCreated()).

With SurfaceView, it's a bit tricky, as the Surface and the View are independent in some aspects: The Surface is placed behind the window that holds the View, and the View hierarchy handles correctly composing the entire layout by punching a hole in the window in order to display SurfaceView's Surface. This might explain why mirroring the content SurfaceView displays isn't possible, though I'm not sure why zooming in (scaleX set to values > 1) and out (scaleX set to values between 0 and 1) work nonetheless.

Husayn Hakeem
  • 4,184
  • 1
  • 16
  • 31
  • The device runs on Android 7.1 and logging `viewFinder.getChildAt(0)` shows `null`. The `scaleX` is set to -1 and... Now it works? But I've just tried the app on my own phone with Android 10 and I've got the same results despite the preview still not flipping. I have no idea. – rdxdkr Jan 05 '21 at 18:57
  • 1
    You should call `getChildAt(0)` only after the preview has started. – Husayn Hakeem Jan 05 '21 at 19:24
  • You mean after calling `bindToLifecycle()`? Still getting `null`. – rdxdkr Jan 05 '21 at 19:51
  • 1
    No, the preview doesn't immediately start after the call to `binToLifecycle()`. You can use `PreviewView.getPreviewStreamState()` and wait for the`STREAMING` state to be dispatched. You can also use any other way to call that method only after you see that the preview starts, e.g. Set a click listener on `PreviewView`, when you see the preview started click on it, inside the click listener you can log its first child. – Husayn Hakeem Jan 05 '21 at 20:08
  • Thanks. Surprisingly enough, on the custom device a `TextureView` is used by default and that might be why it actually works, while the `SurfaceView` that is used on my phone still doesn't want to collaborate. Just to be clear, the only way to reliably mirror a `SurfaceView` would then be to convert each frame to bitmap in real time and draw them back on the surface? – rdxdkr Jan 05 '21 at 22:03
  • I suppose. You'd have to get frames from the camera, mirror and correct them, then draw them on the `SurfaceView`'s `Surface`. Doing so would beat the purpose of using a `SurfaceView` instead of a `TextureView` though, especially if you're just mirroring the preview content. – Husayn Hakeem Jan 06 '21 at 14:14
  • 1
    I've asked a [new question](https://stackoverflow.com/questions/65595525/does-mirroring-the-front-camera-affect-mlkit-with-camerax) with a bit more details on what I'm trying to achieve. I started with CameraX because it should require the least amount of work both for using the camera in general and for calling MLKit APIs, but with these quirks I'm not 100% sure anymore. – rdxdkr Jan 06 '21 at 15:32
  • for me it not work. crashed – K.Sopheak Mar 06 '23 at 09:27