Ориентация поверхности камеры Android
Итак, у меня есть класс, который расширяет SurfaceView и переопределяет
surfaceChanged - просто вызывает startPreview
surfaceCreated - открывает камеру, редактирует параметры *, устанавливает SurfaceHolder
surfaceDestroyed - вызывает stopPreview, освобождает камеру
все это прекрасно работает, потому что, когда ориентация Портретная:
с поверхностиСоздано *
m_camera = Camera.open();
Camera.Parameters p = m_camera.getParameters();
if (getResources().getConfiguration().orientation !=
Configuration.ORIENTATION_LANDSCAPE)
{
p.set("orientation", "portrait");
// CameraApi is a wrapper to check for backwards compatibility
if (CameraApi.isSetRotationSupported())
{
CameraApi.setRotation(p, 90);
}
}
Тем не менее, каждый раз, когда меняется ориентация, он вызывает Camera.open()... что, как вы, возможно, знаете, довольно дорогая операция, из-за которой переходы не такие плавные.
Когда я вынуждаю ориентацию к ландшафту, предварительный просмотр отличный Create вызывается только один раз, и это работает, потому что предварительный просмотр в альбомной ориентации - это всегда то, что видит пользователь. Однако мне нужен способ установить ориентацию фактического снимка, сделанного в портретной ориентации. Однако, когда я навязываю пейзаж, поверхность никогда не воссоздается, и параметры никогда не устанавливаются, когда камера находится в портретном положении.
Итак, как я могу сделать одно из следующих (исключительно)?
Удерживайте m_camera между onDestroy и onCreate при изменении ориентации, чтобы переход был плавным
Принудительное изменение ландшафта и определение изменений ориентации другим способом... вращение окончательного снимка, если он удерживается в портретной ориентации.
Кроме того, если я не с базы, может кто-нибудь направить меня в лучшем направлении? Спасибо.
3 ответа
Как я это реализовал:
private Camera mCamera;
private OrientationEventListener mOrientationEventListener;
private int mOrientation = -1;
private static final int ORIENTATION_PORTRAIT_NORMAL = 1;
private static final int ORIENTATION_PORTRAIT_INVERTED = 2;
private static final int ORIENTATION_LANDSCAPE_NORMAL = 3;
private static final int ORIENTATION_LANDSCAPE_INVERTED = 4;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// force Landscape layout
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR | ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
/*
Your other initialization code here
*/
}
@Override
protected void onResume() {
super.onResume();
if (mOrientationEventListener == null) {
mOrientationEventListener = new OrientationEventListener(this, SensorManager.SENSOR_DELAY_NORMAL) {
@Override
public void onOrientationChanged(int orientation) {
// determine our orientation based on sensor response
int lastOrientation = mOrientation;
if (orientation >= 315 || orientation < 45) {
if (mOrientation != ORIENTATION_PORTRAIT_NORMAL) {
mOrientation = ORIENTATION_PORTRAIT_NORMAL;
}
}
else if (orientation < 315 && orientation >= 225) {
if (mOrientation != ORIENTATION_LANDSCAPE_NORMAL) {
mOrientation = ORIENTATION_LANDSCAPE_NORMAL;
}
}
else if (orientation < 225 && orientation >= 135) {
if (mOrientation != ORIENTATION_PORTRAIT_INVERTED) {
mOrientation = ORIENTATION_PORTRAIT_INVERTED;
}
}
else { // orientation <135 && orientation > 45
if (mOrientation != ORIENTATION_LANDSCAPE_INVERTED) {
mOrientation = ORIENTATION_LANDSCAPE_INVERTED;
}
}
if (lastOrientation != mOrientation) {
changeRotation(mOrientation, lastOrientation);
}
}
};
}
if (mOrientationEventListener.canDetectOrientation()) {
mOrientationEventListener.enable();
}
}
@Override protected void onPause() {
super.onPause();
mOrientationEventListener.disable();
}
/**
* Performs required action to accommodate new orientation
* @param orientation
* @param lastOrientation
*/
private void changeRotation(int orientation, int lastOrientation) {
switch (orientation) {
case ORIENTATION_PORTRAIT_NORMAL:
mSnapButton.setImageDrawable(getRotatedImage(android.R.drawable.ic_menu_camera, 270));
mBackButton.setImageDrawable(getRotatedImage(android.R.drawable.ic_menu_revert, 270));
Log.v("CameraActivity", "Orientation = 90");
break;
case ORIENTATION_LANDSCAPE_NORMAL:
mSnapButton.setImageResource(android.R.drawable.ic_menu_camera);
mBackButton.setImageResource(android.R.drawable.ic_menu_revert);
Log.v("CameraActivity", "Orientation = 0");
break;
case ORIENTATION_PORTRAIT_INVERTED:
mSnapButton.setImageDrawable(getRotatedImage(android.R.drawable.ic_menu_camera, 90));
mBackButton.setImageDrawable(getRotatedImage(android.R.drawable.ic_menu_revert, 90));
Log.v("CameraActivity", "Orientation = 270");
break;
case ORIENTATION_LANDSCAPE_INVERTED:
mSnapButton.setImageDrawable(getRotatedImage(android.R.drawable.ic_menu_camera, 180));
mBackButton.setImageDrawable(getRotatedImage(android.R.drawable.ic_menu_revert, 180));
Log.v("CameraActivity", "Orientation = 180");
break;
}
}
/**
* Rotates given Drawable
* @param drawableId Drawable Id to rotate
* @param degrees Rotate drawable by Degrees
* @return Rotated Drawable
*/
private Drawable getRotatedImage(int drawableId, int degrees) {
Bitmap original = BitmapFactory.decodeResource(getResources(), drawableId);
Matrix matrix = new Matrix();
matrix.postRotate(degrees);
Bitmap rotated = Bitmap.createBitmap(original, 0, 0, original.getWidth(), original.getHeight(), matrix, true);
return new BitmapDrawable(rotated);
}
А затем в вашем PictureCallback установите метаданные, чтобы указать уровень вращения:
private Camera.PictureCallback mJpegCallback = new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
try {
// Populate image metadata
ContentValues image = new ContentValues();
// additional picture metadata
image.put(Media.DISPLAY_NAME, [picture name]);
image.put(Media.MIME_TYPE, "image/jpg");
image.put(Media.TITLE, [picture title]);
image.put(Media.DESCRIPTION, [picture description]);
image.put(Media.DATE_ADDED, [some time]);
image.put(Media.DATE_TAKEN, [some time]);
image.put(Media.DATE_MODIFIED, [some time]);
// do not rotate image, just put rotation info in
switch (mOrientation) {
case ORIENTATION_PORTRAIT_NORMAL:
image.put(Media.ORIENTATION, 90);
break;
case ORIENTATION_LANDSCAPE_NORMAL:
image.put(Media.ORIENTATION, 0);
break;
case ORIENTATION_PORTRAIT_INVERTED:
image.put(Media.ORIENTATION, 270);
break;
case ORIENTATION_LANDSCAPE_INVERTED:
image.put(Media.ORIENTATION, 180);
break;
}
// store the picture
Uri uri = getContentResolver().insert(
Media.EXTERNAL_CONTENT_URI, image);
try {
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0,
data.length);
OutputStream out = getContentResolver().openOutputStream(
uri);
boolean success = bitmap.compress(
Bitmap.CompressFormat.JPEG, 75, out);
out.close();
if (!success) {
finish(); // image output failed without any error,
// silently finish
}
} catch (Exception e) {
e.printStackTrace();
// handle exceptions
}
mResultIntent = new Intent();
mResultIntent.setData(uri);
} catch (Exception e) {
e.printStackTrace();
}
finish();
}
};
Я надеюсь, что это помогает.
ОБНОВЛЕНИЕ Теперь, когда появляются устройства с альбомной ориентацией, требуется дополнительная проверка для этого в OrientationEventListener.
Display display = ((WindowManager)getSystemService(WINDOW_SERVICE)).getDefaultDisplay();
if (display.getOrientation() == Surface.ROTATION_0) {
// landscape oriented devices
} else {
// portrait oriented device
}
Полный код (немного расточителен по LC, но легко демонстрирует подход)
@Override
public void onOrientationChanged(int orientation) {
// determine our orientation based on sensor response
int lastOrientation = mOrientation;
Display display = ((WindowManager)getSystemService(WINDOW_SERVICE)).getDefaultDisplay();
if (display.getOrientation() == Surface.ROTATION_0) { // landscape oriented devices
if (orientation >= 315 || orientation < 45) {
if (mOrientation != ORIENTATION_LANDSCAPE_NORMAL) {
mOrientation = ORIENTATION_LANDSCAPE_NORMAL;
}
} else if (orientation < 315 && orientation >= 225) {
if (mOrientation != ORIENTATION_PORTRAIT_INVERTED) {
mOrientation = ORIENTATION_PORTRAIT_INVERTED;
}
} else if (orientation < 225 && orientation >= 135) {
if (mOrientation != ORIENTATION_LANDSCAPE_INVERTED) {
mOrientation = ORIENTATION_LANDSCAPE_INVERTED;
}
} else if (orientation <135 && orientation > 45) {
if (mOrientation != ORIENTATION_PORTRAIT_NORMAL) {
mOrientation = ORIENTATION_PORTRAIT_NORMAL;
}
}
} else { // portrait oriented devices
if (orientation >= 315 || orientation < 45) {
if (mOrientation != ORIENTATION_PORTRAIT_NORMAL) {
mOrientation = ORIENTATION_PORTRAIT_NORMAL;
}
} else if (orientation < 315 && orientation >= 225) {
if (mOrientation != ORIENTATION_LANDSCAPE_NORMAL) {
mOrientation = ORIENTATION_LANDSCAPE_NORMAL;
}
} else if (orientation < 225 && orientation >= 135) {
if (mOrientation != ORIENTATION_PORTRAIT_INVERTED) {
mOrientation = ORIENTATION_PORTRAIT_INVERTED;
}
} else if (orientation <135 && orientation > 45) {
if (mOrientation != ORIENTATION_LANDSCAPE_INVERTED) {
mOrientation = ORIENTATION_LANDSCAPE_INVERTED;
}
}
}
if (lastOrientation != mOrientation) {
changeRotation(mOrientation, lastOrientation);
}
}
Рассматривали ли вы использование стандартного метода, предоставленного в документе API, который вы можете вызвать на SurfaceChanged? Вы можете сохранить градусы в глобальной переменной для последующего использования при сохранении изображения. Также можно выполнить простую проверку нуля для переменной камеры, чтобы вы не создавали ее снова в SurfaceCreated.
public void setCameraDisplayOrientation()
{
if (mCamera == null)
{
Log.d(TAG,"setCameraDisplayOrientation - camera null");
return;
}
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(CAM_ID, info);
WindowManager winManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
int rotation = winManager.getDefaultDisplay().getRotation();
int degrees = 0;
switch (rotation)
{
case Surface.ROTATION_0: degrees = 0; break;
case Surface.ROTATION_90: degrees = 90; break;
case Surface.ROTATION_180: degrees = 180; break;
case Surface.ROTATION_270: degrees = 270; break;
}
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
{
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360; // compensate the mirror
} else { // back-facing
result = (info.orientation - degrees + 360) % 360;
}
mCamera.setDisplayOrientation(result);
}
Как вы видели из других ответов, этот код становится очень сложным. Возможно, вы захотите изучить использование библиотеки, чтобы помочь вам предоставить эту функцию, например, CWAC-Camera поддерживает OS 2.3 и выше (надеюсь, вы можете отказаться от поддержки OS 2.1 и OS 2.2 сейчас):
https://github.com/commonsguy/cwac-camera
CWAC-Camera поддерживает фиксацию предварительного просмотра камеры в альбомной ориентации и автоматически поворачивает изображения в правильной ориентации. Просмотрите проблемы проекта, если вы хотите попробовать все специфические проблемы устройства, которые должны быть решены. ИМО - это больше причин для того, чтобы попытаться использовать библиотеку вместо того, чтобы поддерживать весь этот код и тестировать себя.