2

I'm trying to set the picture preview in my app to be square, and have the resulting picture exactly match the preview. However, the picture and preview are turning out as shown in the screenshots below.

Preview Preview screenshot

Picture Picture screenshot

The screenshots are from a Nexus 5 running Android 5.0. I've also tested on a Samsung S3 running 4.4.2, and it seems that, whereas with the Nexus the picture captures more than the preview does, on the S3 it seems that the picture is just offset (to the upper left) from the preview. I can post pictures from the S3, if desired.

Here's my code:

public class MainActivity extends Activity  {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    if (savedInstanceState == null) {
        getFragmentManager().beginTransaction()
                .add(R.id.container, new PlaceholderFragment())
                .commit();
    }
}


@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.menu_main, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();

    //noinspection SimplifiableIfStatement
    if (id == R.id.action_settings) {
        return true;
    }

    return super.onOptionsItemSelected(item);
}

/**
 * A placeholder fragment containing a simple view.
 */
public static class PlaceholderFragment extends CameraFragment {

    private FrameLayout cameraPreviewLayout;
    private Camera camera;

    @Override
    public void onCreate(Bundle state) {
        super.onCreate(state);

        SimpleCameraHost.Builder builder=
                new SimpleCameraHost.Builder(new TestCameraHost(getActivity()));

        setHost(builder.useFullBleedPreview(true).build());
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View cameraView=
                super.onCreateView(inflater, container, savedInstanceState);
        View rootView = inflater.inflate(R.layout.fragment_main, container, false);

        cameraPreviewLayout = ((FrameLayout)rootView.findViewById(R.id.camera_layout));
        cameraPreviewLayout.addView(cameraView);

        ImageButton changeLayout = (ImageButton) rootView.findViewById(R.id.pick_existing);
        changeLayout.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

            }
        });

        Button takePicture = (Button) rootView.findViewById(R.id.shutter_button);
        takePicture.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                PictureTransaction xact = new PictureTransaction(getHost());
                takePicture(xact);
            }
        });

        return(rootView);
    }

    @Override
    public void onResume() {
        // Get the window width to make the camera preview window square
        DisplayMetrics metrics = new DisplayMetrics();
        getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics);
        final int windowWidth = metrics.widthPixels;
        cameraPreviewLayout.post(new Runnable() {
            @Override
            public void run() {
                ViewGroup.LayoutParams params = cameraPreviewLayout.getLayoutParams();
                params.height = windowWidth;
                cameraPreviewLayout.setLayoutParams(params);
            }
        });

        super.onResume();
    }

    @Override
    public void onPause() {
        if (camera != null) {
            camera.release();
        }
        super.onPause();
    }

    private class TestCameraHost extends SimpleCameraHost {
        public TestCameraHost(Context context) {
            super(context);
        }

        @Override
        public void saveImage(PictureTransaction xact, byte[] image) {

            DisplayActivity.imageToShow = image;
            DisplayActivity.x = (int) cameraPreviewLayout.getX();
            DisplayActivity.y = (int) cameraPreviewLayout.getY();
            DisplayActivity.height = (int) cameraPreviewLayout.getHeight();
            DisplayActivity.width = (int) cameraPreviewLayout.getWidth();
            startActivity(new Intent(getActivity(), DisplayActivity.class));

        }
    }
}

}

public class DisplayActivity extends Activity {
static byte[] imageToShow=null;
static int x = 0;
static int y = 0;
static int width = 0;
static int height = 0;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    if (imageToShow == null) {
        Toast.makeText(this, "no image!", Toast.LENGTH_LONG).show();
        finish();
    }
    else {
        ImageView iv=new ImageView(this);
        BitmapFactory.Options opts=new BitmapFactory.Options();

        opts.inPurgeable=true;
        opts.inInputShareable=true;
        opts.inMutable=false;
        opts.inSampleSize=2;

        Bitmap bitmap = BitmapFactory.decodeByteArray(imageToShow,
                0, imageToShow.length, opts);
        Bitmap croppedBitmap = Bitmap.createBitmap(bitmap, x, y, width, height);

        iv.setImageBitmap(croppedBitmap);
        imageToShow=null;

        iv.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
        setContentView(iv);
    }
}

}

<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"
tools:context=".MainActivity$PlaceholderFragment">

<FrameLayout
    android:id="@+id/camera_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

<RelativeLayout
    android:id="@+id/footer"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:padding="16dp"
    android:background="#ffffff">

    <ImageButton
        android:id="@+id/pick_existing"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_centerVertical="true"
        android:src="@drawable/ic_launcher"/>

    <Button
        android:id="@+id/shutter_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Take picture"/>

    <ToggleButton
        android:id="@+id/flash_toggle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:padding="20dp"
        android:layout_centerVertical="true"
        android:textOff=""
        android:textOn=""/>

    </RelativeLayout>
</RelativeLayout>

I've experimented with adjustPreviewParameters, but there doesn't seem to be any choice that allows me to set the preview to be square.

Can I do what I'm trying to do?

hBrent
  • 1,696
  • 1
  • 17
  • 38
  • any update on this issue? Im pulling my hair out with a similar issue! – Chris Jan 21 '15 at 13:37
  • @Chris I haven't fully implemented a solution. The 1 answer below may prove useful, though I didn't try to fully implement it, and am not sure I understand it. I've mentioned it to someone I consider much more of an expert in these things than I am, and he had some concerns about the feasibility of it. You may want to contact the answer poster directly, though. – hBrent Jan 22 '15 at 00:04
  • Thanks very much for the update Brent! I think I'm having the same problem as you - Im not sure I understand the solution haha. I'm going to work on it a bit more, if I find a solution I'll report it back to you – Chris Jan 22 '15 at 07:14
  • Not sure if you still have the code for this, but have you tried setting useFullBleedPreview(true) to false...? If I understand it correctly, the last paragraph under the full-bleed-preview section seems to describe your scenario: https://github.com/commonsguy/cwac-camera#supporting-full-bleed-preview – Chris Jan 22 '15 at 08:30
  • @Chris I don't think I've tried that. I forgot that my question was about CWAC camera in particular, not just Android camera. I chatted with the CWAC camera author about this shortly after I posted this question, and he expressed doubt about being able to do what I want to do with his library. He said that CWAC camera is for more basic functionality, that "if more than 1% of your app functionality is tied up in using the camera, my library (current and future) isn't really what you're looking for" (see http://commonsware.com/office-hours/2014-11-25). – hBrent Jan 22 '15 at 16:57
  • @Chris If you are just using CWAC camera and all you need is to capture a square image, you can try my solution. My solution is working on many devices (at least I got no complaint so far). – John Pang Mar 24 '16 at 20:26

1 Answers1

4
  1. in getPreviewSize() must pick one of the provided ratio, not 1:1. I would look for a 4:3 which will gives the widest view of the camera.

  2. in getPictureSize() must pick one of the resolution with same ratio as your preview size. Otherwise, the output may got a totally different picture.

  3. Use FrameLayout or RelativeLayout so that you can have two layers: bottom layer is the CameraView, upper layer will have a square, transparent view and remain is the "blocking" area. If you set the "block view" to a semi-transparent color, you will understand why.

  4. Best ratio to use is 4:3 (not 16:9)

  5. Finally, the best and simplest way is to use/crop the top square area from the picture. It seems rational to use the center area, but using the top square will save you lots of troubles.

Test Platform: Sony Xperia Z, Android 4.2.2

Test Resolution: 1080x1920.

Available Preview: 1280x720, 960x720, 720x480, 704x576, 640x480, 480x320, 320x240, 176x144.

Target Preview size: 1080x1080, a square, of course :D

Test done inside CameraHost.getPreviewSize() of my custom CameraHost.

  1. If I provide a square view/size to the super class function here, I got 704x576. It is 11:9 (or 1.22222~) which is closest to 1:1 or 1.0. However, it looks blurry when enlarge to 1080 width and also distorted.

  2. 1280x720 is 16:9 (1.777~). The device camera got this by cropping the top and bottom (please note that camera is talking in landscape mode).

  3. 960x720 is 4:3 (1.333~). This is the full resolution of the device camera. It is my target resolution, where I only want the 720x720 inside 960x720 towards the top edge(portrait) or left edge(landscape).

  4. In your custom CameraHost (extends SimpleCameraHost), limit the preview size:

    @Override
    public Size getPreviewSize(int orientation, int w, int h, Parameters p) {
        Size size = super.getPreviewSize(orientation, 720, 960, p);
        ViewGroup.LayoutParams params = cameraView.getLayoutParams();
        params.width = 1080;
        params.height = 960 * 1080/720;
        cameraView.setLayoutParams(params);
        return size;
    }
    
  5. layout.xml:

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <com.commonsware.cwac.camera.CameraView
            android:id="@+id/cameraView"
            android:layout_width="match_parent"
            android:layout_gravity="top|center_horizontal"
            android:layout_height="match_parent"/>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
            <com.example.android.widget.SquareView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@android:color/transparent"/>
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1"
                android:orientation="vertical"
                android:background="#99e0e0e0">
                (your elements to cover up the extra area)
            </LinearLayout>
        </LinearLayout>
    </FrameLayout>
    

Following is the result and comparing with the system camera, using 4:3.

Screen using this code Default camera app with 4:3

Note:

  1. I suppose you know how to create your own SquareView so I won't post the code of it here. You can read more here: Simple way to do dynamic but square layout
  2. The piece of code in #4 is just test code - you need to figure out how it can be adapt for all devices.
  3. Do NOT accept click event by the CameraView for focusing. Use the SquareView instead.
Community
  • 1
  • 1
John Pang
  • 2,403
  • 25
  • 25
  • Thanks for the detailed answer. It may take me a bit of time to absorb all of it, but it makes sense to me to put a square "window" over the camera preview to display the square area as the preview that the user can actually see. Let me try to implement this and see that it does what I need before I take further action on your answer. – hBrent Nov 24 '14 at 16:54
  • @hBrent Sure. Take your time. Please feel free to let me know if you encounter any problem. I'm working on a project with similar requirement. So, I'm open to share any experience with you. – John Pang Nov 27 '14 at 21:27