Question

I would like to use a custom camera to take pictures of a lesser size than the preview,I have heard that maintaining the aspect ratio is very important.I have used the PreviewFrameLayout in AOSP ICS.I have found that the SurfaceView only displays at the center of the activity in a box and does not fill the entire screen.

public class PreviewFrameLayout extends RelativeLayout{

static final String TAG="PreviewFrameLayout";
public interface OnSizeChangedListener
{
    public void onSizeChanged(int w,int h);
}

OnSizeChangedListener mListener;

public void setOnSizeChangedListener(OnSizeChangedListener listener)
{
    mListener=listener;
}

private double mAspectRatio=4.0/3.0;

public PreviewFrameLayout(Context c,AttributeSet attrs)
{
    super(c,attrs);
}

public void setAspectRatio(double ratio)
{
    if(ratio<=0.0)
        throw new IllegalArgumentException();
    if(getResources().getConfiguration().orientation==Configuration.ORIENTATION_PORTRAIT)
        ratio=1/ratio;
    if(mAspectRatio!=ratio)
    {
        mAspectRatio=ratio;
        requestLayout();
    }
}

@Override
protected void onMeasure(int widthSpec,int heightSpec)
{
    int previewWidth=MeasureSpec.getSize(widthSpec);
    int previewHeight=MeasureSpec.getSize(heightSpec);

    int hPadding=getPaddingLeft()+getPaddingRight();
    int vPadding=getPaddingTop()+getPaddingBottom();

    previewWidth-=hPadding;
    previewHeight-=vPadding;

    if(previewWidth>previewHeight*mAspectRatio)
        previewWidth=(int) (previewHeight*mAspectRatio+.5);
    else
        previewHeight=(int)(previewWidth/mAspectRatio+.5);
    previewWidth+=hPadding;
    previewHeight+=vPadding;
    Log.d(TAG,"Aspect ratio "+mAspectRatio);
    Log.d(TAG, "Width: "+previewWidth+" Height: "+previewHeight);
    super.onMeasure(MeasureSpec.makeMeasureSpec(previewWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(previewHeight, MeasureSpec.EXACTLY));
}

protected void onSizeChanged(int w,int h,int oldw,int oldh)
{
    if(mListener!=null)
        mListener.onSizeChanged(w, h);
}
}

and have set the preview and picture size like this:

  private Size getOptimalPreviewSize(List<Size> supportedSizeList,double targetRatio)
{
    final double ASPECT_TOLERANCE=0.05;
    double minDiff=Double.MAX_VALUE;
    Size optimalSize=null;
    Display display=getWindowManager().getDefaultDisplay();
    @SuppressWarnings("deprecation")
    int targetHeight=Math.min(display.getWidth(), display.getHeight());
    if(targetHeight<=0)
    {   
        WindowManager windowManager=(WindowManager)getSystemService(Context.WINDOW_SERVICE);
        targetHeight=windowManager.getDefaultDisplay().getHeight();
    }
    for(Size size:supportedSizeList)
    {
        double ratio=(double)size.width/size.height;
        if(Math.abs(targetRatio-ratio)>ASPECT_TOLERANCE)
            continue;
        if(Math.abs(size.height-targetHeight)<minDiff)
        {
            optimalSize=size;
            minDiff=Math.abs(size.height-targetHeight);
        }
    }

    for(Size size:supportedSizeList)
    {
        if((Math.abs(size.height-targetHeight))<minDiff)
        {   
            optimalSize=size;
            minDiff=Math.abs(size.height-targetHeight);
        }
    }
    return optimalSize;
}

private Size getDesiredPictureSize(List<Size> supportedSizeList)
{
    //Resolution is widthxheight

    Size result=null;
    final int minArea=500*500;  
    final int maxArea=1000*1000;
    for(Size size:supportedSizeList)
    {
        if(size.width*size.height>minArea && size.width*size.height<maxArea)
        {
            if(result==null)
                result=size;
            else
            {
                int resultArea=result.width*result.height;
                int sizeArea=size.width*size.height;
                if(resultArea<sizeArea)
                {   
                    result=size;
                }   
            }
        }
    }
    return result;
 }

And I used the code here to set the aspect ratio to the picture size:

    mParameters=mCamera.getParameters();
    List<Size> supportedPictureSizes=mParameters.getSupportedPictureSizes();
    List<Size> supportedPreviewSizes=mParameters.getSupportedPreviewSizes();
    mPictureSize=getDesiredPictureSize(supportedPictureSizes);
    double targetRatio=(double)mPictureSize.width/mPictureSize.height;
    mPreviewPanel=findViewById(R.id.frame_layout);
    mPreviewFrameLayout=(PreviewFrameLayout)findViewById(R.id.frame);
    mPreviewFrameLayout.setAspectRatio(targetRatio);

The parent of this layout is a RelativeLayout:

<com.example.newcameraproject.PreviewFrameLayout android:id="@+id/frame"
        android:layout_centerInParent="true"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    <SurfaceView android:id="@+id/camera_preview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
      </com.example.newcameraproject.PreviewFrameLayout>

This is included in the main camera layout like this:

  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
>

 <include layout="@layout/preview_frame"/>

 </LinearLayout>

EDIT:

Portrait Orientation:

CameraActivity(6502): Resolution Width: 720 Resolution Height: 1184
CameraActivity(6502): The Picture Width: 1152 The Picture Height: 864
CameraActivity(6502): The Preview Width:960 Preview Height: 720
CameraActivity(6502): Picture Ratio: 1.3333333333333333
CameraActivity(6502): Preview Ratio: 1.3333333333333333
PreviewFrameLayout(6502): Left: 0 Right: 0 Top: 0 Bottom: 0
PreviewFrameLayout(6502): Aspect ratio 0.75
PreviewFrameLayout(6502): Width: 720 Height: 960

The aspect ratio is inverted because it is in the portrait orientation.

if(getResources().getConfiguration().orientation==Configuration.ORIENTATION_PORTRAIT)
        ratio=1/ratio;

The Camera displays as a box just smaller than the activity.

Landscape Orientation:

  CameraActivity(6502): Resolution Width: 1196 Resolution Height: 720
  CameraActivity(6502): The Picture Width: 1152 The Picture Height: 864
  CameraActivity(6502): The Preview Width:960 Preview Height: 720
  CameraActivity(6502): Picture Ratio: 1.3333333333333333
  CameraActivity(6502): Preview Ratio: 1.3333333333333333
  PreviewFrameLayout(6502): Left: 0 Right: 0 Top: 0 Bottom: 0
  PreviewFrameLayout(6502): Aspect ratio 1.3333333333333333
  PreviewFrameLayout(6502): Width: 787 Height: 590

 mPreviewSize=getOptimalPreviewSize(this,supportedPreviewSizes, targetRatio);
    Log.d(TAG, "The Preview Width:"+mPreviewSize.width+" Preview Height: "+mPreviewSize.height);
    double ratio=(double)mPreviewSize.width/mPreviewSize.height;
    Log.d(TAG,"Picture Ratio: "+targetRatio);
    Log.d(TAG, "Preview Ratio: "+ratio);
    int new_width=0, new_height=0;    
 if(getResources().getConfiguration().orientation==Configuration.ORIENTATION_PORTRAIT)
    {
        if((double)previewFrame.getWidth()/previewFrame.getHeight()<ratio)
        {
            new_width=(int)(Math.round(previewFrame.getHeight()*ratio));
            new_height=getWindowManager().getDefaultDisplay().getHeight();
        }
        else
        {
            new_width=getWindowManager().getDefaultDisplay().getHeight();
            new_height=(int)Math.round((double)new_width/ratio);
        }
    }
    if(getResources().getConfiguration().orientation==Configuration.ORIENTATION_LANDSCAPE)
    {
        if((double)previewFrame.getWidth()/previewFrame.getHeight()<ratio)
        {
            new_width=(int)(Math.round(previewFrame.getHeight()*ratio));
            new_height=getWindowManager().getDefaultDisplay().getHeight();
        }
        else
        {
            new_width=getWindowManager().getDefaultDisplay().getWidth();
            new_height=(int)Math.round((double)new_width/ratio);
        }
    }

EDIT:

1.Ensured that getOptimalPreviewSize and other methods works,changed code outside of this post to do so.
2.Even though these methods now work,the preview was not filling the screen,so changed to FrameLayout.The final version(see above) now works for both landscape and portrait.
3.I have noticed that the image in the preview looks stretched when the Camera Activity is rotated to landscape.

Added a question to address this at Camera Preview stretched when using FrameLayout

EDIT2:

The Camera PreviewFrameLayout works almost perfectly(it is almost full screen)...just above the ShutterButton when I added this code to the constructor:

 public PreviewFrameLayout(Context c,AttributeSet attrs)
{
    super(c,attrs);
    setAspectRatio(4.0/3.0);
}

There still are problems such as taking a picture in landscape where the screen has a display orientation of 90 and previewing it...

I find that the image obtained is rotated 90 degrees clockwise in both landscape and portrait modes despite using the ExifInterface and Matrix to fix this rotation code in ImagePreviewActivity

The Exif orientation is always 0,I fixed this the last time(without the PreviewFrameLayout using Parameters.setRotation code when setting orientation...but it does not seem to be working here

Was it helpful?

Solution

I cannot see where getOptimalPreviewSize() is called; also, there is no real reason to set preview size to match the screen; usually, the default camera preview is chosen by the manufacturer to look good on the screen (which involves scaling).

With all that, the code you published could be significantly simplified. If I understand correctly, you have fixed screenOrientation in your activity, and want to fill all the screen with camera preview, preserving aspect ratio to match the desired picture size.

This simple example, shows how to set width and height for the preview surface view. If this is not enough for you, feel free to ask for more details.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top