I am trying to get camera previews to work properly in portrait mode, where the activity itself is allowed to change orientation normally (i.e., not be locked to landscape).
The use of setDisplayOrientation()
seriously breaks the behavior of the previews, though.
This can be demonstrated by Google's own ApiDemos. The first image is based on the CameraPreview
from the android-17
edition of the ApiDemos
sample project, where the only change I made was to remove android:orientation="landscape"
from this activity's entry in the manifest. The following image is a screenshot of what appears on the display of a Nexus 4, running Android 4.2, with the camera pointed at a 8.5" square of paper:
What is not obvious from that preview is that it is rotated 90 degrees. The sock-clad feet that you see on the far right of the image were actually below the square.
The solution for this, at least for landscape mode, is to use setDisplayOrientation()
. But, if you modify the Preview
inner class of CameraPreview
to have mCamera.setDisplayOrientation(90);
, you get this:
Notably, the square is no longer square.
This uses the same SurfaceView
size as before. I have reproduced this scenario with some other code, outside of ApiDemos
, and I get the same behavior with TextureView
in that code.
I thought briefly that the issue might be that getSupportedPreviewSizes()
might return different values based upon setDisplayOrientation()
, but a quick test suggests that this is not the case.
Anyone have any idea how to get setDisplayOrientation()
to work in portrait mode without wrecking the aspect ratio of the imagery pushed over to the preview?
Thanks!
If you are taking portrait photography, with the camera of your smartphone, the aspect ratio of it is 2:3; meaning the picture is shown with 500 × 750, or 1500 × 2250, in pixels.
Find and turn on the "Auto-rotate" tile in the quick-setting panel. You can also go to Settings > Display > Auto-rotate screen to turn it on. Your phone screen should rotate automatically now if nothing is wrong with the sensors.
With graphics or printing, portrait mode is the orientation of the page that prints the image vertically across the page instead of horizontally. By default, all programs have the page orientation and printer pages set up in portrait mode.
Magisto supports the following orientations and aspect ratios: 1) Landscape - 16:9 (1920×1080, 1280×720, etc.) 2) Portrait - 9:16 (1080x1920, 720x1280, etc.)
OK, I think that I figured this out. There were a few pieces to the proverbial puzzle, and I thank @kcoppock for the insight that helped me solve some of this.
First, you need to take the orientation into account when determining what preview size to choose. For example, the getOptimalPreviewSize()
from the CameraPreview
of ApiDemos
is oblivious to orientation, simply because their version of that app has the orientation locked to landscape. If you wish to let the orientation float, you need to reverse the target aspect ratio to match. So, where getOptimalPreviewSize()
has:
double targetRatio=(double)width / height;
you also need:
if (displayOrientation == 90 || displayOrientation == 270) {
targetRatio=(double)height / width;
}
where displayOrientation
is a value from 0 to 360, that I am determining from about 100 lines of some seriously ugly code, which is why I am wrapping all of this up in a reusable component that I'll publish shortly. That code is based on the setDisplayOrientation()
JavaDocs and this StackOverflow answer.
(BTW, if you are reading this after 1 July 2013, and you don't see a link in this answer to that component, post a comment to remind me, as I forgot)
Second, you need to take that display orientation into account when controlling the aspect ratio of the SurfaceView
/TextureView
that you are using. The CameraPreview
activity from ApiDemos
has its own Preview
ViewGroup
that handles the aspect ratio, and there you need to reverse the aspect ratio for use in portrait:
if (displayOrientation == 90
|| displayOrientation == 270) {
previewWidth=mPreviewSize.height;
previewHeight=mPreviewSize.width;
}
else {
previewWidth=mPreviewSize.width;
previewHeight=mPreviewSize.height;
}
where displayOrientation
is that same value (90
and 270
being portrait and reverse-portrait respectively, and note that I haven't tried getting reverse-portrait or reverse-landscape to work, so there may be more tweaking required).
Third -- and one that I find infuriating -- you must start the preview before calling setPictureSize()
on the Camera.Parameters
. Otherwise, it's as if the aspect ratio of the picture is applied to the preview frames, screwing things up.
So I used to have code resembling the following:
Camera.Parameters parameters=camera.getParameters();
Camera.Size pictureSize=getHost().getPictureSize(parameters);
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
parameters.setPictureSize(pictureSize.width, pictureSize.height);
parameters.setPictureFormat(ImageFormat.JPEG);
camera.setParameters(parameters);
camera.startPreview();
and that's wrong. What you really need is:
Camera.Parameters parameters=camera.getParameters();
Camera.Size pictureSize=getHost().getPictureSize(parameters);
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
camera.setParameters(parameters);
camera.startPreview();
parameters=camera.getParameters();
parameters.setPictureSize(pictureSize.width, pictureSize.height);
parameters.setPictureFormat(ImageFormat.JPEG);
camera.setParameters(parameters);
Without that change, I'd get a stretched aspect ratio, even in landscape. This wasn't a problem for CameraPreview
of ApiDemos
, as they were not taking pictures, and so they never called setPictureSize()
.
But, so far, on a Nexus 4, I now have square aspect ratios for portrait and landscape, with a floating orientation (i.e., not locked to landscape).
I'll try to remember to amend this question if I run into other hacks required by other devices, plus to link to my CameraView
/CameraFragment
components when they are released.
UPDATE #1
Well, of course, it couldn't possibly by nearly this simple. :-)
The fix for the problem where setPictureSize()
screws up the aspect ratio works... until you take a picture. At that point, the preview switches to the wrong aspect ratio.
One possibility would be to limit pictures to ones with the same (or very close) aspect ratio to the preview, so the hiccup does not exist. I don't like that answer, as the user should be able to get whatever picture size the camera offers.
You can limit the scope of the damage by:
Camera.Parameters
just before taking the pictureCamera.Parameters
after taking the pictureThis still presents the wrong aspect ratio during the moments while the picture is being taken. The long-term solution for this -- I think -- will be to temporarily replace the camera preview with a still image (the last preview frame) while the picture is being taken. I'll try this eventually.
UPDATE #2: v0.0.1 of my CWAC-Camera library is now available, incorporating the aforementioned code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With