14

I would like to effectively make a simple digital zoom for the camera preview, so I thought I would simply resize my SurfaceView to be larger than the screen. Other questions (such as 3813049) seem to indicate that this is easy, so I created the sample code below which I expect to let me see only half of the image horizontally (since the SurfaceView is twice as wide as the screen) and have the image only take up half of the screen horizontally. However, running it (when targeted to SDK version 4 on my Thunderbolt with Android 2.2.1) results in being able to see the whole image horizontally while filling the screen horizontally. The SurfaceView appears to behave as intended vertically (when I make it smaller than the screen), but Android won't allow me to make the SurfaceView larger than the screen.

How can I implement a digital zoom? (No, I cannot use Camera.Parameters.setZoom; not only is this not supported by Android 1.6, but different cameras support and implement this differently)

public class MagnifyTestActivity extends Activity implements SurfaceHolder.Callback {
    private MagnificationView mPreview;
    private SurfaceHolder mHolder;
    private Camera mCamera = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPreview = new MagnificationView(this);
        setContentView(mPreview);
        mHolder = mPreview.getHolder();
        mHolder.addCallback(this);
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    public class MagnificationView extends SurfaceView {
        public MagnificationView(Context context) {
            super(context);
        }

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            Display display = getWindowManager().getDefaultDisplay();
            int width = display.getWidth()*2;
            int height = display.getHeight()/2;
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }

    public void surfaceCreated(SurfaceHolder holder) {
        mCamera = Camera.open();
        try {
            mCamera.setPreviewDisplay(holder);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        mCamera.stopPreview();
        mCamera.release();
        mCamera = null;
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        mHolder.setFixedSize(w, h);
        mCamera.startPreview();
    }
}

UPDATE: Based on @Pulkit Sethi's response, it is possible to stretch/magnify the SurfaceView vertically, just not horizontally. To magnify the SurfaceView vertically, simply replace display.getHeight()/2 with display.getHeight()*2 above. Also observe that changing the width doesn't produce any horizontal magnification, either in my code or in Pulkit's.

Community
  • 1
  • 1
Ben
  • 1,272
  • 13
  • 28
  • did you ever manage to solve this ? I would like to know also, Thanks – NewDev Jul 01 '11 at 03:21
  • Anyone? I'm also looking to make a surfaceView larger than the visible bounds of its parent. – Kevin Parker Oct 06 '11 at 17:21
  • 1
    Unfortunately, I was never able to make a SurfaceView larger than the screen. – Ben Dec 10 '11 at 17:44
  • Have you looked at adjusting what is actually being displayed on the screen? This could be linked to a couple of variables. X and Y locations and a Scale attribute would be suggested. I imagine that each method that is drawn onto your surfaceView will need to account for these variables. – Music Monkey Nov 22 '12 at 10:02
  • I'm not sure what you're suggesting. I do not have access to whatever code (probably native) that draws the image data from the camera onto the SurfaceView, so I can't change any attributes in that code. The heart of this question is why trying to change the width of the SurfaceView to be bigger than the screen doesn't work. To clarify your comment, could you propose specific code that I could change or add to my example above to get the desired results? – Ben Dec 19 '12 at 19:56

4 Answers4

3
//Activity class

public class CameraActivity extends Activity implements SurfaceListener {

    private static final String TAG = "CameraActivity";

    Camera mCamera;
    CameraPreview mPreview;
    private FrameLayout mCameraPreview;


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

        setContentView(R.layout.activity_camera);

        mCamera = getCameraInstance();
        mPreview = new CameraPreview(this, mCamera);

        mCameraPreview = (FrameLayout) findViewById(R.id.camera_preview);
        mCameraPreview.addView(mPreview);


    }

    @Override
    protected void onPause() {
        super.onPause();

        releaseCamera();
    }

    private Camera getCameraInstance() {
        Camera camera = null;
        try {
            camera = Camera.open();
        } catch (Exception e) {

        }
        return camera;
    }

    private void releaseCamera() {
        if (null != mCamera) {
            mCamera.release();
        }

        mCamera = null;
    }

    @Override
    public void surfaceCreated() {

        //Change these mate
        int width = 1000;
        int height = 1000;
        // Set parent window params
        getWindow().setLayout(width, height);

        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
                width, height);
        mCameraPreview.setLayoutParams(params);
        mCameraPreview.requestLayout();
    }
}

// Preview class

public class CameraPreview extends SurfaceView implements
        SurfaceHolder.Callback {

    private static final String TAG = "CameraPreview";

    Context mContext;
    Camera mCamera;
    SurfaceHolder mHolder;

    public interface SurfaceListener{
        public void surfaceCreated();
    }
    SurfaceListener listener;


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

        mContext = context;
        listener = (SurfaceListener)mContext;
        mCamera = camera;

        mHolder = getHolder();
        mHolder.addCallback(this);

        // Required prior 3.0 HC
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {

        try {
            mCamera.setPreviewDisplay(holder);

            Parameters params = mCamera.getParameters();
            //Change parameters here
            mCamera.setParameters(params);

            mCamera.startPreview();

            listener.surfaceCreated();

        } catch (Exception e) {
            // TODO: handle exception
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // empty. Take care of releasing the Camera preview in your activity.
    }

    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.

        Log.i(TAG, "Surface changed called");
        if (mHolder.getSurface() == null) {
            // preview surface does not exist
            return;
        }

        // stop preview before making changes
        try {
            mCamera.stopPreview();
        } catch (Exception e) {
            // ignore: tried to stop a non-existent preview
        }

        // set preview size and make any resize, rotate or
        // reformatting changes here
        mCamera.setDisplayOrientation(90);

        // start preview with new settings
        try {
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();

        } catch (Exception e) {
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }
    }

}

//Layout file 
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <FrameLayout
        android:id="@+id/camera_preview"
        android:layout_width="300dp"
        android:layout_height="400dp"
        android:layout_centerHorizontal="true"
        android:paddingTop="10dp" >
    </FrameLayout>

</RelativeLayout>
Pulkit Sethi
  • 1,325
  • 1
  • 14
  • 22
  • Have you actually had this work? Don't just guess code and post it as an answer. Your definition for layoutParams throws a ClassCastException, plus you never use layoutParams. Your use of params also throws a ClassCastException because mPreview is MagnificationView (which is a SurfaceView) in my example -- not a RelativeLayout -- unless your code redefines it and is therefore incomplete. Also, there appears to be no reason to add an extra interface layer on SurfaceHolder.Callback's surfaceCreated. Please post COMPLETE and TESTED code if you want to try again. – Ben Mar 25 '13 at 06:32
  • will do yes it works for me, hard to put stuff required for you out of my code – Pulkit Sethi Mar 25 '13 at 23:07
  • Your getting a class cast exception because you are not using RelativeLayout as parent view, like I am, but now I will attach the complete code, the activity, preview class and the xml. Please include this in your application and just call the activity – Pulkit Sethi Mar 26 '13 at 00:42
  • Ok, I can reproduce the fact that this code does magnify the camera preview vertically. Most of it is unnecessary though, and removing all of that reveals that the reason it sort-of-works is that SurfaceViews can apparently be magnified vertically, but not horizontally. To reproduce the same effect with my original code, just change display.getHeight()/2 to display.getHeight()*2. Your code is just an extremely round-about way of doing that, so I'm afraid it doesn't fully answer the question because it can't magnify the image horizontally. – Ben Mar 26 '13 at 04:44
  • It can be magnified both ways. I will only give you a hint as to set the camera preview to the correct value. The reason i do this using an interface is app specific and there is a lot of other calculations as well. Seth the preview size to get the desired behaviour – Pulkit Sethi Mar 26 '13 at 12:03
1

You can't make your surfaceView bigger than the screen. That being said there are ways around it.

I found you can adjust the size of the canvas in the SurfaceView, which will allow zooming.

public class DrawingThread extends Thread {
private MagnificationView mainPanel;
private SurfaceHolder surfaceHolder;
private boolean run;

public DrawingThread(SurfaceHolder surface, MagnificationView panel){
    surfaceHolder = surface;
    mainPanel = panel;
}

public SurfaceHolder getSurfaceHolder(){
    return surfaceHolder;
}

public void setRunning (boolean run){
    this.run = run;
}

public void run(){
    Canvas c;
    while (run){
        c = null;
        try {
            c = surfaceHolder.lockCanvas(null);
            synchronized (surfaceHolder){
                mainPanel.OnDraw(c);
            }
        } finally {
            if (c != null){
                surfaceHolder.unlockCanvasAndPost(c);
            }
        }
    }
}

}

In the MagnificationView class add a method:

public void OnDraw(Canvas canvas){
    if (canvas!=null){
        canvas.save();
        canvas.scale(scaleX,scaleY);      

        canvas.restore();
    }
}

DrawingThread would be a thread you start in in your Activity. Also in your MagnificationView class override the OnTouchEvent to handle your own pinch-zoom (which will modify scaleX & scaleY.

Hope This solves your issue

sgbel2
  • 121
  • 7
  • Nice, that looks really promising! Have you verified that this does indeed work with the camera (the camera respects the Canvas transform when it writes to the surface)? I'd like to try it out before accepting your answer, but thanks a ton for the info! – Ben Jan 11 '13 at 00:34
  • I had meant to verify it when I posted it, but was in the middle of a project I was trying to get finished.. will check it this weekend for you to verify :) – sgbel2 Jan 11 '13 at 05:00
1

What you can do is to get the window and set its height:

getWindow().setLayout(1000, 1000);

This makes your window larger than the screen making your root view and consequently your surfaceview, probably contained inside a Framelayout larger than screen.

This worked for me let me know.

The above would work no matter what. What you would want to do is listen for onSurfaceCreated event for your surface view. Then after you have the started the camera view and you are able to calculate size of your widget holding the preview, you would want to change size of the container widget.

The concept is your container widget (probably FrameLayout) wants to grow larger than screen. The screen itself is restricted by the activity so first set size of your window,

then set size of your framelayout (it would always be shrunk to max size of windows, so set accordingly).

I do all this logic after my onSurfaceCreated is finished I have started the preview. I listen for this event in my activity by implementing a small interface, as my Camera preview is a separate class.

Working on all API level >= 8

MHSaffari
  • 858
  • 1
  • 16
  • 39
Pulkit Sethi
  • 1,325
  • 1
  • 14
  • 22
  • 2
    Where did you put that line of code, and what SDK version did you use on what device when it worked for you? When I put that line of code directly after super.onCreate, it appears to do nothing. Same result when I put it directly after setContentView. Did you actually have a SurfaceView on your Activity? I'll bet your suggestion would work for normal Views, but SurfaceView appears to be special. – Ben Mar 21 '13 at 08:52
  • edited the answer please check thanks, its working for surface view the one showing the camera view, havent tried on any other widgets – Pulkit Sethi Mar 21 '13 at 22:32
  • Please be more specific as your suggestion is currently too vague for me to test. Is your suggestion similar to my code? If so, where exactly did you insert which lines of additional code? If not, please post the code that worked for you. Also, why did you choose 1000, 1000? To positively check whether the magnification is working, the virtual size of the SurfaceView should be at least twice as big as the screen; for 2x magnification, the objects in the camera's view center should appear on the far right of the screen. – Ben Mar 22 '13 at 22:45
  • If you cannot get this work still, i will email you code and working video – Pulkit Sethi Mar 26 '13 at 00:47
0

Here's my TouchSurfaceView's onMeasure that performs zoom:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension((int) (width * scaleFactor), (int) (height * scaleFactor));
    }

This properly zooms in and out depending on scaleFactor.

I haven't tested this with camera, but it works properly with MediaPlayer (behaving as VideoView).

ernazm
  • 9,208
  • 4
  • 44
  • 51