Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: switch width and height of target resolution if portrait for Android #833

Closed
wants to merge 3 commits into from

Conversation

xulihang
Copy link
Contributor

What

This PR fixes the incorrect resolution setting for Android.

Changes

switch height and width of target resolution if portrait for Android

Tested on

Sharp S2, Android 8

Related issues

#770
#325
#281

@mrousavy
Copy link
Owner

What happens when the user rotates his phone after the Camera has been initialized?

@xulihang
Copy link
Contributor Author

In my case, the camera still works in the specified resolution.

@xulihang
Copy link
Contributor Author

In the docs, it says that the resolution Size should be expressed in the coordinate frame after rotating the supported sizes by the target rotation: https://developer.android.com/reference/kotlin/androidx/camera/core/ImageAnalysis.Builder#setTargetResolution(android.util.Size)

@ldstein
Copy link

ldstein commented Feb 18, 2022

FWIW I've been using a hack which shares similarities with what @xulihang proposes.

https://gist.github.com/ldstein/bf8b88fabc27617dca891aa3be58f9d7

Seemed to get me out of trouble for the limited devices I have on hand.

I noticed in this PR, Surface.ROTATION_0 is being used to determine if the width and height should be reversed. This works for devices whose natural orientation is potrait but may have the opposite effect for devices which are landscape natural orientation (I say "may" because I have no such devices on hand to test).

An alternative would be to reference context.resources.configuration.orientation as this will inform us explicitly if the device is currently in Landscape or Portrait.

What happens when the user rotates his phone after the Camera has been initialized?

As @xulihang mentions, it appears to work. Maybe under the hood it's already flipping it around when targetRotation is set during updateOrientation()?

I do wonder however if imageAnalysis.targetRotation and preview.targetRotation should both be set to inputRotation to keep them 1:1, but maybe that's a different topic.

@xulihang
Copy link
Contributor Author

This works for devices whose natural orientation is potrait but may have the opposite effect for devices which are landscape natural orientation

In the docs: https://developer.android.com/training/camera2/camera-preview-large-screens#camera-orientation, it says that the camera sensor’s natural orientation is landscape.

@ldstein
Copy link

ldstein commented Feb 18, 2022

In the above link it states:

The Android Compatibility Definition specifies that a camera image sensor “MUST be oriented so that the long dimension of the camera aligns with the screen’s long dimension.”

If you have an Android tablet which is naturally landscape orientated, this logic I think will incorrectly set the wrong resolution:

// Asume:
// Device Rotation: 0
// Device Screen Size: 1024x768
// Camera Sensor Orientation: Landscape (same as screen)
// format.videoSize.width:1024
// format.videoSize.height: 768

if (getDisplay().rotation == Surface.ROTATION_0) {
          val newWidth = format.videoSize.height // 768
          val newHeight = format.videoSize.width // 1024
          targetPreviewResolution = Size(newWidth, newHeight) // resolution is incorrectly set to portrait

I think that's why my original hack fixed the resolution for phones, but did not work on @viljark's TB-X505L tablet.

format.videoSize appears to always be reported in landscape, so I think we only need to set a portrait targetResolution when the device is in portrait:

// Asume:
// Device Rotation: 0
// Device Screen Size: 1024x768
// Camera Sensor Orientation: Landscape (same as screen)
// format.videoSize.width:1024
// format.videoSize.height: 768

val videoSize = when (context.resources.configuration.orientation) {
    Configuration.ORIENTATION_PORTRAIT -> Size(format.videoSize.height, format.videoSize.width) // resolution is set to portrait
    else -> format.videoSize // resolution is set to landscape
}

Having said all this, I'm no expert, so happy to be proven wrong :)

@xulihang
Copy link
Contributor Author

Using orientation seems a better solution. I also find a page on detecting the natural orientation: http://www.java2s.com/example/android/user-interface/get-device-natural-orientation.html

@xulihang
Copy link
Contributor Author

I've updated the code to use orientation.

@@ -410,9 +411,15 @@ class CameraView(context: Context, private val frameProcessorThread: ExecutorSer
// User has selected a custom format={}. Use that
val format = DeviceFormat(format!!)
Log.i(TAG, "Using custom format - photo: ${format.photoSize}, video: ${format.videoSize} @ $fps FPS")
previewBuilder.setTargetResolution(format.videoSize)
val videoSize:Size
if (context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, thanks again for the PR.

Unfortunately I am still not quite sure if I understand this correctly, doesn't this only check the "locked" Orientation in AndroidManifest? What if the user is holding his phone in portrait, then going to landscape? And then going back to portrait? Is it still the correct resolution?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I read the docs correctly, context.resources.configuration.orientation returns the device's current orientation.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, but can we use outputOrientation instead of context.resources.configuration.orientation here? I already detect orientation in this class

Copy link

@ldstein ldstein Feb 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

outputRotation is referencing context.displayRotation. That tells us how many degrees the device is rotated from it's natural position but I believe we cannot assume zero rotation is portrait.

context.resources.configuration.orientation differs as it tells us explicitly whether the device is currently being viewed in portrait or landscape.

Similarly, there maybe an issue here in the outputRotation getter. Portrait would only equal Surface.ROTATION_0 for naturally-portrait devices:

  private val outputRotation: Int
    get() {
      if (orientation != null) {
        // user is overriding output orientation
        return when (orientation!!) {
          "portrait" -> Surface.ROTATION_0
          "landscapeRight" -> Surface.ROTATION_90
          "portraitUpsideDown" -> Surface.ROTATION_180
          "landscapeLeft" -> Surface.ROTATION_270

Also not 100% sure on this, but maybe imageAnalysis.targetRotation should be the same as preview.targetRotation ? Keeps the analysis results 1:1 with the preview (when drawing overlays, for example).

I can see the use of the 'orientation' prop to affect photo / video output, but maybe should be independent to modifying the frame processor rotation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot find outputOrientation. There is orientation which is a string and outputRotation which is an integer.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, typo on my end. I fixed up my earlier post. I meant outputRotation

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay guys thanks for the insights. I will test this some time this week and then merge the PR if everything works in every use-case :)

Would be helpful if anyone could help me with testing, with various devices and various orientations (e.g. start with landscape or portrait, then rotate)

Copy link

@siddharth-kt siddharth-kt Mar 31, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mrousavy @xulihang What are your views on this PR, is it ready to get merged or ...?

ethul added a commit to ethul/react-native-vision-camera that referenced this pull request Apr 1, 2022
Use `ImageProxy.imageInfo.rotationDegrees` to apply the appropriate
rotation to the image in order to align the orientation of the image
with the target orientation.

Potential alternative to
mrousavy#833
ethul added a commit to ethul/react-native-vision-camera that referenced this pull request Apr 1, 2022
Use `ImageProxy.imageInfo.rotationDegrees` to apply the appropriate
rotation to the image in order to align the orientation of the image
with the target orientation.

Potential alternative to
mrousavy#833
ethul added a commit to ethul/react-native-vision-camera that referenced this pull request May 12, 2022
Use `ImageProxy.imageInfo.rotationDegrees` to apply the appropriate
rotation to the image in order to align the orientation of the image
with the target orientation.

Potential alternative to
mrousavy#833
ethul added a commit to ethul/react-native-vision-camera that referenced this pull request May 26, 2022
Use `ImageProxy.imageInfo.rotationDegrees` to apply the appropriate
rotation to the image in order to align the orientation of the image
with the target orientation.

Potential alternative to
mrousavy#833
@bekatd
Copy link

bekatd commented May 27, 2022

Camera formats and everything related to it are are completely wrong.

  • width and heights are swapped,
  • unable to set desired formats,
  • also photoWidth and photoHeight of useCameraFormat hook are totally wrong, since the width and height of frame in frameprocessor plugin is for example 640x640 while current cameraformat is 2000x4000 :|
  • also camera feed strangely zooms on different formats
  • also if you log all available formats and look at the format returned by default, you will discover that it can't be found in the available formats :|
  • also on every different device everything works differently and wrongly :|

how all of this is even possible :|

@mrousavy
Copy link
Owner

Hey, thanks for your PR and sorry for the long radio silence here! width and height are swapped because Camera sensors are in 90deg orientation. Orientation is now tracked in this issue: #1891

@mrousavy mrousavy closed this Sep 30, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants