0

I have been working on a piece of code to preview and capture photo inside my fragment. I have made this by following Airpair guides on camera.

Problem is: Android Camera preview is rotated by 90degree and therefore looks stretched as well in certain devices.

Note: I tried this on my Samsung Galaxy Alpha (4.9 inch screen) and it worked properly. However in Huawei P8 (5 inch screen) and nexus 5x (5.2 inch screen) it was showing 90 degree rotated stretched preview.

I have seen others posting similar issue on `SO', however those solutions have already been integrated to this code.

for instance overriding protected void onLayout(boolean changed, int left, int top, int right, int bottom) is used to fix the rotation.

I would appreciate if you could point out what I've been missing.

/**
* A simple {@link Fragment} subclass.
*/
public class KycCameraFragment extends Fragment {

public static final String TAG = "KycCameraFragment";

// Native camera.
private Camera mCamera;

// View to display the camera output.
private CameraPreview mPreview;

// Reference to the containing view.
private View mCameraView;

public KycCameraFragment() {
    // Required empty public constructor
}

@Override

public void onStart() {
    super.onStart();
    // register the event to listen.
    GlobalBus.getBus().register(this);
}



@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    View view = inflater.inflate(R.layout.fragment_kyc_camera, container, false);


    // Create our Preview view and set it as the content of our activity.
    boolean opened = safeCameraOpenInView(view);

    if(opened == false){
        Log.d("CameraGuide","Error, Camera failed to open");
        return view;
    }

    // Trap the capture button.
    Button captureButton = (Button) view.findViewById(R.id.button_capture);
    captureButton.setOnClickListener(
            new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // get an image from the camera
                    mCamera.takePicture(null, null, mPicture);
                }
            }
    );

    return view;
}

@Subscribe(sticky = true)
public void getMessage(Events.FragmentIdTypeMessage fragmentIdTypeMessage) {
    TextView _tvEKYCCamera = (TextView) getView().findViewById(R.id.tvEKYCCamera);
    _tvEKYCCamera.setText("Message from Fragment ID type" + " " + fragmentIdTypeMessage.getMessage());
    Toast.makeText(getContext(),
            "Message Received: " + fragmentIdTypeMessage.getMessage(),
            Toast.LENGTH_SHORT).show();
}

@Override
public void onDestroyView() {
    super.onDestroyView();
    // unregister the registered event.
    releaseCameraAndPreview();
    GlobalBus.getBus().unregister(this);
}

/**
 * Recommended "safe" way to open the camera.
 * @param view
 * @return
 */
private boolean safeCameraOpenInView(View view) {
    boolean qOpened = false;
    releaseCameraAndPreview();
    mCamera = getCameraInstance();
    mCameraView = view;
    qOpened = (mCamera != null);

    if(qOpened == true){
        mPreview = new CameraPreview(getActivity().getBaseContext(), mCamera,view);
        FrameLayout preview = (FrameLayout) view.findViewById(R.id.camera_preview);
        preview.addView(mPreview);
        mPreview.startCameraPreview();
    }
    return qOpened;
}

/**
 * Safe method for getting a camera instance.
 * @return
 */
public static Camera getCameraInstance(){
    Camera c = null;
    try {
        c = Camera.open(); // attempt to get a Camera instance
    }
    catch (Exception e){
        e.printStackTrace();
    }
    return c; // returns null if camera is unavailable
}

/**
 * Clear any existing preview / camera.
 */
private void releaseCameraAndPreview() {

    if (mCamera != null) {
        mCamera.stopPreview();
        mCamera.release();
        mCamera = null;
    }
    if(mPreview != null){
        mPreview.destroyDrawingCache();
        mPreview.mCamera = null;
    }
}

/**
 * Surface on which the camera projects it's capture results. This is derived both from Google's docs and the
 * excellent StackOverflow answer provided below.
 *
 * Reference / Credit: http://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
 */
class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {

    // SurfaceHolder
    private SurfaceHolder mHolder;

    // Our Camera.
    private Camera mCamera;

    // Parent Context.
    private Context mContext;

    // Camera Sizing (For rotation, orientation changes)
    private Camera.Size mPreviewSize;

    // List of supported preview sizes
    private List<Camera.Size> mSupportedPreviewSizes;

    // Flash modes supported by this camera
    private List<String> mSupportedFlashModes;

    // View holding this camera.
    private View mCameraView;

    public CameraPreview(Context context, Camera camera, View cameraView) {
        super(context);

        // Capture the context
        mCameraView = cameraView;
        mContext = context;
        setCamera(camera);

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        mHolder = getHolder();
        mHolder.addCallback(this);
        mHolder.setKeepScreenOn(true);
        // deprecated setting, but required on Android versions prior to 3.0
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    /**
     * Begin the preview of the camera input.
     */
    public void startCameraPreview()
    {
        try{
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }

    /**
     * Extract supported preview and flash modes from the camera.
     * @param camera
     */
    private void setCamera(Camera camera)
    {
        // Source: http://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
        mCamera = camera;
        mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
        mSupportedFlashModes = mCamera.getParameters().getSupportedFlashModes();

        // Set the camera to Auto Flash mode.
        if (mSupportedFlashModes != null && mSupportedFlashModes.contains(Camera.Parameters.FLASH_MODE_AUTO)){
            Camera.Parameters parameters = mCamera.getParameters();
            parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
            mCamera.setParameters(parameters);
        }

        requestLayout();
    }

    /**
     * The Surface has been created, now tell the camera where to draw the preview.
     * @param holder
     */
    public void surfaceCreated(SurfaceHolder holder) {
        try {
            mCamera.setPreviewDisplay(holder);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Dispose of the camera preview.
     * @param holder
     */
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (mCamera != null){
            mCamera.stopPreview();
        }
    }

    /**
     * React to surface changed events
     * @param holder
     * @param format
     * @param w
     * @param h
     */
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.

        if (mHolder.getSurface() == null){
            // preview surface does not exist
            return;
        }

        // stop preview before making changes
        try {
            Camera.Parameters parameters = mCamera.getParameters();

            // Set the auto-focus mode to "continuous"
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);

            // Preview size must exist.
            if(mPreviewSize != null) {
                Camera.Size previewSize = mPreviewSize;
                parameters.setPreviewSize(previewSize.width, previewSize.height);
            }

            mCamera.setParameters(parameters);
            mCamera.startPreview();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * Calculate the measurements of the layout
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        // Source: http://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
        final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
        final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
        setMeasuredDimension(width, height);

        if (mSupportedPreviewSizes != null){
            mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
        }
    }

    /**
     * Update the layout based on rotation and orientation changes.
     * @param changed
     * @param left
     * @param top
     * @param right
     * @param bottom
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom)
    {
        // Source: http://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
        if (changed) {
            final int width = right - left;
            final int height = bottom - top;

            int previewWidth = width;
            int previewHeight = height;

            if (mPreviewSize != null){
                Display display = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();

                switch (display.getRotation())
                {
                    case Surface.ROTATION_0:
                        previewWidth = mPreviewSize.height;
                        previewHeight = mPreviewSize.width;
                        mCamera.setDisplayOrientation(90);
                        break;
                    case Surface.ROTATION_90:
                        previewWidth = mPreviewSize.width;
                        previewHeight = mPreviewSize.height;
                        break;
                    case Surface.ROTATION_180:
                        previewWidth = mPreviewSize.height;
                        previewHeight = mPreviewSize.width;
                        break;
                    case Surface.ROTATION_270:
                        previewWidth = mPreviewSize.width;
                        previewHeight = mPreviewSize.height;
                        mCamera.setDisplayOrientation(180);
                        break;
                }
            }

            final int scaledChildHeight = previewHeight * width / previewWidth;
            mCameraView.layout(0, height - scaledChildHeight, width, height);
        }
    }

    /**
     *
     * @param sizes
     * @param width
     * @param height
     * @return
     */
    private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int width, int height)
    {
        // Source: http://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
        Camera.Size optimalSize = null;

        final double ASPECT_TOLERANCE = 0.1;
        double targetRatio = (double) height / width;

        // Try to find a size match which suits the whole screen minus the menu on the left.
        for (Camera.Size size : sizes){

            if (size.height != width) continue;
            double ratio = (double) size.width / size.height;
            if (ratio <= targetRatio + ASPECT_TOLERANCE && ratio >= targetRatio - ASPECT_TOLERANCE){
                optimalSize = size;
            }
        }

        // If we cannot find the one that matches the aspect ratio, ignore the requirement.
        if (optimalSize == null) {
            // TODO : Backup in case we don't get a size.
        }

        return optimalSize;
    }
}

/**
 * Picture Callback for handling a picture capture and saving it out to a file.
 */
private Camera.PictureCallback mPicture = new Camera.PictureCallback() {

    @Override
    public void onPictureTaken(byte[] data, Camera camera) {

        File pictureFile = getOutputMediaFile();
        if (pictureFile == null){
            Toast.makeText(getActivity(), "Image retrieval failed.", Toast.LENGTH_SHORT)
                    .show();
            return;
        }

        try {
            FileOutputStream fos = new FileOutputStream(pictureFile);
            fos.write(data);
            fos.close();

            // Restart the camera preview.
            safeCameraOpenInView(mCameraView);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
};

/**
 * Used to return the camera File output.
 * @return
 */
private File getOutputMediaFile(){

    File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_PICTURES), "MM");

    if (! mediaStorageDir.exists()){
        if (! mediaStorageDir.mkdirs()){
            Log.d("CameraGuide", "Required media storage does not exist");
            return null;
        }
    }

    // Create a media file name
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    File mediaFile;
    mediaFile = new File(mediaStorageDir.getPath() + File.separator +
            "IMG_"+ timeStamp + ".jpg");

    Toast.makeText(getActivity(),"Your picture has been saved!", Toast.LENGTH_LONG).show();
//        DialogHelper.showDialog( "Success!","Your picture has been saved!",getActivity());

        return mediaFile;
    }
}

Fragment Layout:

RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
>

<!-- TODO: Update blank fragment layout -->
<TextView
    android:id="@+id/tvEKYCCamera"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:text="@string/hello_blank_fragment" />

<FrameLayout
    android:id="@+id/camera_preview"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_weight="1"
    />

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_centerInParent="true"
    android:orientation="vertical"
    android:gravity="center"
    >
    <View
        android:id="@+id/myRectangleView"
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:background="@drawable/rectangle"
        />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Place ID Within Here"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_alignParentBottom="true"
android:orientation="horizontal"
android:gravity="center">
    <Button
        android:id="@+id/button_capture"
        android:text=""
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_margin="10dp"
        android:background="@drawable/camera_button"
        />
</LinearLayout>

Poorya
  • 1,291
  • 6
  • 27
  • 57
  • Are you sure image rotation doesn't come from camera Orientation? Have you checked Image orientation from image EXIF metadata? – Keivan Esbati Apr 29 '17 at 09:46
  • @KeivanEsbati Well Image is saved in landscape. so i guess you are right ! so in this case how to make sure camera orientation is vertical (portrait) ? – Poorya Apr 29 '17 at 09:48
  • Here, Check this guide. Hope it helps you: https://inducesmile.com/android/how-to-set-camera-image-orientation-with-exif-metadata/ – Keivan Esbati Apr 29 '17 at 09:52
  • @KeivanEsbati but this example is about how to set the orientation of the picture using EXIF. in my case i need to handle this in surfaceview for live preview. – Poorya Apr 29 '17 at 10:03

1 Answers1

0

Well after some experimenting I figured out that by setting the case Surface.ROTATION_90: while overriding the protected void onLayout(boolean changed, int left, int top, int right, int bottom) solved the orientation issue.

apparently some camera's come with default orientation of 0degree and some 90 degree.

here is what i've changed for people who might face the same issue:

        @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom)
    {
        // Source: http://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
        if (changed) {
            final int width = right - left;
            final int height = bottom - top;

            int previewWidth = width;
            int previewHeight = height;

            if (mPreviewSize != null){
                Display display = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();

                switch (display.getRotation())
                {
                    case Surface.ROTATION_0:
                        previewWidth = mPreviewSize.height;
                        previewHeight = mPreviewSize.width;
                        mCamera.setDisplayOrientation(90);
                        break;
                    case Surface.ROTATION_90:
                        previewWidth = mPreviewSize.width;
                        previewHeight = mPreviewSize.height;
                        //added to fix rotation issue
                        mCamera.setDisplayOrientation(90);
                        break;
                    case Surface.ROTATION_180:
                        previewWidth = mPreviewSize.height;
                        previewHeight = mPreviewSize.width;
                        //added to fix rotation issue
                        mCamera.setDisplayOrientation(180);
                        break;
                    case Surface.ROTATION_270:
                        previewWidth = mPreviewSize.width;
                        previewHeight = mPreviewSize.height;
                        mCamera.setDisplayOrientation(180);
                        break;
                }
            }

            final int scaledChildHeight = previewHeight * width / previewWidth;
            mCameraView.layout(0, height - scaledChildHeight, width, height);
        }


    }
Poorya
  • 1,291
  • 6
  • 27
  • 57