dimanche 3 octobre 2010

Prendre une photo avec l'API Camera Android

Dans ce tutorial, nous allons voir comment utiliser l'API Android pour prendre des photos avec le téléphone. Pour que cela soit possible, il faut déclarer dans le manifest de l'application la permission suivante :
<uses-permission android:name="android.permission.CAMERA" />
Afin de s'assurer que l'application soit utilisable uniquement par les téléphones disposant d'un appareil photo numérique (APN), il suffit d'ajouter la ligne suivante dans le manifest :
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
La seconde permet de s'assurer que l'appareil photo dispose d'un auto focus.

Pour utiliser la camera Android, il faut une SurfaceView afin d'afficher en temps réel la preview de la camera et un bouton, pour indiquer à quel moment la photo doit être prise. L'API permet de récupérer différentes données :
Vous trouverez ci-dessous le code d'une SurfaceView permettant la prise de photo au moyen de l'API Camera Android :
import java.io.IOException;
import java.util.List;

import android.content.Context;
import android.hardware.Camera;
import android.hardware.Camera.Size;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

/**
 * 
 * Android Camera API archetype
 * 
 * Under GPL v3 : http://www.gnu.org/licenses/gpl-3.0.html
 * 
 * @author antoine vianey
 *
 */
public final class CameraLivePreview extends SurfaceView 
        implements SurfaceHolder.Callback {
    
    private SurfaceHolder holder;
    private Camera camera;

    /**
     * Retrieve raw picture data after shooting
     */
    private Camera.PictureCallback rawCallback = 
            new Camera.PictureCallback() {
        public void onPictureTaken(byte[] data, Camera c) {
            // work with raw data
            // ...
        }
    };
    
    /**
     * Retrieve jpeg compress data after shooting
     */
    private Camera.PictureCallback jpegCallback = 
            new Camera.PictureCallback() {
        public void onPictureTaken(byte[] data, Camera c) {
            // start the camera preview
            camera.startPreview();
            // work with the jpeg data
            // ...
        }
    };
    
    /**
     * Retrieve frame data for each frame
     */
    private Camera.PreviewCallback frameCallback = 
            new Camera.PreviewCallback() {
        public void onPreviewFrame(byte[] data, Camera camera) {
            // work with the frame data
            // ...
        }
    };
    
    /**
     * Retrieve information about shutter
     */
    private Camera.ShutterCallback shutterCallback = 
            new Camera.ShutterCallback() {
        public void onShutter() {
            // handle shutter done
            // ...
        }
    };
    
    /**
     * Ensure it is supported by adding 
     * android.hardware.camera.autofocus feature
     * to the application manifest
     */
    private Camera.AutoFocusCallback autoFocusCallback = 
            new Camera.AutoFocusCallback() {
        public void onAutoFocus(boolean success, Camera camera) {
            // handle focus done
            // you can choose to take a picture
            // after auto focus is completed
            camera.takePicture(shutterCallback, rawCallback, jpegCallback);
        }
    };

    public CameraLivePreview(Context context) {
        super(context);
        init();
    }

    public CameraLivePreview(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public CameraLivePreview(Context context, AttributeSet attrs, 
            int defStyle) {
        super(context, attrs, defStyle);
        init();
    }
    
    private void init() {
        holder = getHolder();
        holder.addCallback(this);
        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }
    
    public void surfaceCreated(SurfaceHolder holder) {
        // surface created
        // we can tell the camera to render
        // into the surface
        // but it's not ready to preview yet
        camera = Camera.open();
        try {
            camera.setPreviewDisplay(holder);
        } catch (IOException exception) {
            camera.release();
            camera = null;
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // surface destroyed
        // we must tell the camera to stop it preview
        camera.stopPreview();
        camera.release();
        camera = null;
    }

    public void surfaceChanged(SurfaceHolder holder, 
            int format, int w, int h) {
        // we get the surface dimensions
        // we can configure the preview
        Camera.Parameters parameters = camera.getParameters();

        List<Size> sizes = parameters.getSupportedPreviewSizes();
        Size optimalSize = getOptimalPreviewSize(sizes, w, h);
        parameters.setPreviewSize(optimalSize.width, optimalSize.height);

        camera.setParameters(parameters);
        
        // let render
        camera.startPreview();
        camera.setPreviewCallback(frameCallback);
    }

    private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
        
        // requirement
        final double ASPECT_TOLERANCE = 0.05;
        
        double targetRatio = (double) w / h;
        if (sizes == null) {
            return null;
        }

        Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;

        int targetHeight = h;

        // find a size that match aspect ratio and size
        for (Size size : sizes) {
            double ratio = (double) size.width / size.height;
            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
                continue;
            if (Math.abs(size.height - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }

        // it's not possible
        // ignore the requirement
        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Size size : sizes) {
                if (Math.abs(size.height - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }
        }
        
        return optimalSize;
    }

    public void takePhoto() {
        // take a photo :
        // 1 - auto focus
        // 2 - take the picture in the auto focus callback
        camera.autoFocus(autoFocusCallback);
    }

}
N'hésitez pas à partager vos création utilisant l'API Camera.

2 commentaires:

  1. merci pour ce code. mais comment fait-on pour prendre une photo avec l'émulateur, il me donne un carré qui bouge sur un arriere plan en forme de jeu d'échec, puis il s'arrête en erreur .....

    merci

    RépondreSupprimer
  2. A ma connaissance, il n'est pas possible de prendre une photo avec l’émulateur. Il faut avoir un téléphone Android en mode débug...

    RépondreSupprimer

Fork me on GitHub