Search code examples
androidcameraorientationsurfaceview

The ideal surfaceView Camera orientations and proportions (avoiding preview stretching) - android


I've started a project which contains of a camera surfaceView (used in order to live image preview on the screen) from beginning I thought it shouldn't be much problems in implementing this, but for now I've got stuck within the part concerning the preview orientation and it's proportions, I've been searching Stack's topics about surfaceview orientations and stretching issues but I couldn't get a good result out of them at all.

What I would like to do is to obtain preview in the same angle, no matter of the phone rotating, so the user could see his face all the time like that.

Example picture

I would be glad if You could please suggest me how could I change my code to avoid the preview stretching in the portret and landscape views aswell.

I'm attatching my code above

public class CameraView extends SurfaceView implements SurfaceHolder.Callback {


private SurfaceHolder mHolder;
private Camera mCamera;


public CameraView (Context context, Camera camera){
    super(context);
    mCamera = camera;
    mCamera.setDisplayOrientation(90);


    //get the holder and set this class as the callback, so we can get camera data here
    mHolder = getHolder();
    mHolder.addCallback(this);
    //mHolder.setType(SurfaceHolder.SURFACE_TYPE_NORMAL); 
    mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}

@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
    try {
        Camera.Parameters parameters = mCamera.getParameters();
        if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
            parameters.set("orientation", "portrait");
            mCamera.setDisplayOrientation(90);
            parameters.setRotation(90);
        }
        else {
            // This is an undocumented although widely known feature
            parameters.set("orientation", "landscape");
            // For Android 2.2 and above
            mCamera.setDisplayOrientation(0);
            // Uncomment for Android 2.0 and above
            parameters.setRotation(0);
        }
        mCamera.setPreviewDisplay(surfaceHolder);
        mCamera.startPreview();

    } catch (IOException e) {
        // left blank for now
    }
}

@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
    this.getHolder().removeCallback(this); 
    mCamera.stopPreview();
    mCamera.release();
}

@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int format,
                           int width, int height) {

    try {
        Camera.Parameters parameters = mCamera.getParameters();
        if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
            parameters.set("orientation", "portrait");
            mCamera.setDisplayOrientation(90);
            parameters.setRotation(90);
            setCamera(mCamera);
            mCamera.setPreviewDisplay(surfaceHolder);
            mCamera.startPreview();
        }
        else {

            parameters.set("orientation", "landscape");
            mCamera.setDisplayOrientation(0);
            parameters.setRotation(0);
        }
        setCamera(mCamera);
        mCamera.setPreviewDisplay(surfaceHolder);
        mCamera.startPreview();

    } catch (IOException e) {

    }
}

public void setCamera(Camera camera){ 
    mCamera = camera;  }               

Solution

  • In your approach you would have to choose the specific ratio that you want to display in your preview, this will produce results which vary based on the screen size.

    You may want to consider a different approach, you can define your own camera preview as follows:

    • Create a camera preview class:

         public class CameraSourcePreview extends ViewGroup {
      
      
      
              private static final String TAG = "CameraSourcePreview";
              private Context mContext;
              private SurfaceView mSurfaceView;
              private boolean mStartRequested;
              private boolean mSurfaceAvailable;
              private CameraSource mCameraSource;
      
              private GraphicOverlay mOverlay;
      
              public CameraSourcePreview(Context context, AttributeSet attrs) {
                  super(context, attrs);
                  mContext = context;
                  mStartRequested = false;
                  mSurfaceAvailable = false;
      
                  mSurfaceView = new SurfaceView(context);
                  mSurfaceView.getHolder().addCallback(new SurfaceCallback());
                  addView(mSurfaceView);
              }
      
              public void start(CameraSource cameraSource) throws IOException {
                  if (cameraSource == null) {
                      stop();
                  }
      
                  mCameraSource = cameraSource;
      
                  if (mCameraSource != null) {
                      mStartRequested = true;
                      startIfReady();
                  }
              }
      
              public void start(CameraSource cameraSource, GraphicOverlay overlay) throws IOException {
                  mOverlay = overlay;
                  start(cameraSource);
              }
      
              public void stop() {
                  if (mCameraSource != null) {
                      mCameraSource.stop();
                  }
              }
      
              public void release() {
                  if (mCameraSource != null) {
                      mCameraSource.release();
                      mCameraSource = null;
                  }
              }
      
              private void startIfReady() throws IOException {
                  if (mStartRequested && mSurfaceAvailable) {
                      mCameraSource.start(mSurfaceView.getHolder());
                      if (mOverlay != null) {
                          Size size = mCameraSource.getPreviewSize();
                          int min = Math.min(size.getWidth(), size.getHeight());
                          int max = Math.max(size.getWidth(), size.getHeight());
                          if (isPortraitMode()) {
                              // Swap width and height sizes when in portrait, since it will be rotated by
                              // 90 degrees
                              mOverlay.setCameraInfo(min, max, mCameraSource.getCameraFacing());
                          } else {
                              mOverlay.setCameraInfo(max, min, mCameraSource.getCameraFacing());
                          }
                          mOverlay.clear();
                      }
                      mStartRequested = false;
                  }
              }
      
              private class SurfaceCallback implements SurfaceHolder.Callback {
                  @Override
                  public void surfaceCreated(SurfaceHolder surface) {
                      mSurfaceAvailable = true;
                      try {
                          startIfReady();
                      } catch (IOException e) {
                          Log.e(TAG, "Could not start camera source.", e);
                      }
                  }
      
                  @Override
                  public void surfaceDestroyed(SurfaceHolder surface) {
                      mSurfaceAvailable = false;
                  }
      
                  @Override
                  public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                  }
              }
      
              @Override
              protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
                  int previewWidth = 320;
                  int previewHeight = 240;
                  if (mCameraSource != null) {
                      Size size = mCameraSource.getPreviewSize();
                      if (size != null) {
                          previewWidth = size.getWidth();
                          previewHeight = size.getHeight();
                      }
                  }
      
                  // Swap width and height sizes when in portrait, since it will be rotated 90 degrees
                  if (isPortraitMode()) {
                      int tmp = previewWidth;
                      previewWidth = previewHeight;
                      previewHeight = tmp;
                  }
      
                  final int viewWidth = right - left;
                  final int viewHeight = bottom - top;
      
                  int childWidth;
                  int childHeight;
                  int childXOffset = 0;
                  int childYOffset = 0;
                  float widthRatio = (float) viewWidth / (float) previewWidth;
                  float heightRatio = (float) viewHeight / (float) previewHeight;
      
                  // To fill the view with the camera preview, while also preserving the correct aspect ratio,
                  // it is usually necessary to slightly oversize the child and to crop off portions along one
                  // of the dimensions.  We scale up based on the dimension requiring the most correction, and
                  // compute a crop offset for the other dimension.
                  if (widthRatio > heightRatio) {
                      childWidth = viewWidth;
                      childHeight = (int) ((float) previewHeight * widthRatio);
                      childYOffset = (childHeight - viewHeight) / 2;
                  } else {
                      childWidth = (int) ((float) previewWidth * heightRatio);
                      childHeight = viewHeight;
                      childXOffset = (childWidth - viewWidth) / 2;
                  }
      
                  for (int i = 0; i < getChildCount(); ++i) {
                      // One dimension will be cropped.  We shift child over or up by this offset and adjust
                      // the size to maintain the proper aspect ratio.
                      getChildAt(i).layout(
                              -1 * childXOffset, -1 * childYOffset,
                              childWidth - childXOffset, childHeight - childYOffset);
                  }
      
                  try {
                      startIfReady();
                  } catch (IOException e) {
                      Log.e(TAG, "Could not start camera source.", e);
                  }
              }
      
              private boolean isPortraitMode() {
                  int orientation = mContext.getResources().getConfiguration().orientation;
                  if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
                      return false;
                  }
                  if (orientation == Configuration.ORIENTATION_PORTRAIT) {
                      return true;
                  }
      
                  Log.d(TAG, "isPortraitMode returning false by default");
                  return false;
              }
      

      }

    • In your layout.xml add the custom preview:

      <yourpackagename.CameraSourcePreview
          android:id="@+id/preview"
          android:layout_width="match_parent"
          android:layout_height="match_parent">
      

    • Inside your activity class, define the camera source method, you can then call the method in onCreate()

    /** * Starts or restarts the camera source, if it exists. If the camera source doesn't exist yet * (e.g., because onResume was called before the camera source was created), this will be called * again when the camera source is created. */

    private void startCameraSource() {
        // check that the device has play services available.
        int code = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(
                getApplicationContext());
        if (code != ConnectionResult.SUCCESS) {
            Dialog dlg =
                    GoogleApiAvailability.getInstance().getErrorDialog(this, code, RC_HANDLE_GMS);
            dlg.show();
        }
    
        if (mCameraSource != null) {
            try {
                mPreview.start(mCameraSource, mGraphicOverlay);
            } catch (IOException e) {
                Log.e(TAG, "Unable to start camera source.", e);
                mCameraSource.release();
                mCameraSource = null;
            }
        }
    }
    
    • Don't forget to add the following:

    /** * Restarts the camera. */

    @Override
    protected void onResume() {
        super.onResume();
    
        startCameraSource();
    }
    

    /** * Stops the camera. */

    @Override
    protected void onPause() {
        super.onPause();
        mPreview.stop();
    }
    
    /**
     * Releases the resources associated with the camera source, the associated detector, and the
     * rest of the processing pipeline.
     */
    
    
     @Override
        protected void onDestroy() {
            super.onDestroy();
            if (mCameraSource != null) {
                mCameraSource.release();
            }
        }