[Windows Phone 7.5] Acceso a la Cámara (I)

Hola a todos!

Hoy vamos a hacer un alto en nuestra series de artículos sobre Expression Blend 4 para ver como, con Windows Phone 7.5, podemos tener acceso a la cámara de nuestro dispositivo de dos modos distintos. Veremos como sacar fotos, ajustar parámetros de la cámara y como grabar vídeos.

Dos apis, un hardware

En Windows Phone 7.5 tenemos acceso a la cámara de nuestro dispositivo a través de dos apis diferentes: La api de cámara de Windows Phone y la api de webcam de Silverlight 4. Esto se debe a que internamente en nuestra aplicación usamos Silverlight 4 para desarrollar y este ya incluye soporte para acceso a cámaras. De esta forma, usaremos la api propia de Windows Phone para obtener capturas, fotos, de la cámara y podremos usar la api de webcam para grabar vídeos.

¿Cuando usar una u otra? Las diferencias entre ambas vienen marcadas por la calidad con la que accederemos a las imágenes en vivo. La api de Windows Phone nos ofrecerá imágenes en mayor resolución (tanta como soporte nuestra cámara), podremos acceder al botón de disparo físico del dispositivo, controlar el flash, el zoom o el foco. Sin embargo el api de webcam de Silverlight 4 nos ofrecerá video en una resolución más baja, pero con mayor ratio de cuadros por segundo por lo que podremos grabar videos más fluidos que incluyan audio.

Api de cámara de Windows Phone

El uso del api nativa de Windows Phone para acceder a la cámara es realmente sencillo. Disponemos de un nuevo tipo de brocha que podemos aplicar como “color” a nuestros elementos, por ejemplo al foreground de un textblock se trata del VideoBrush:

<TextBlock Text="Camara"
            VerticalAlignment="Center"
            HorizontalAlignment="Center"
            FontSize="190">
    <TextBlock.Foreground>
        <VideoBrush x:Name="videoBrush"></VideoBrush>
    </TextBlock.Foreground>
</TextBlock>

O al Background de una Grid:

<Grid>
    <Grid.Background>
        <VideoBrush x:Name="videoBrush"></VideoBrush>
    </Grid.Background>
</Grid>

De esta forma, podemos obtener la fuente de imágenes de la cámara. Ahora solo necesitamos inicializar nuestra cámara, usando el objeto PhotoCamera localizado en el namespace Microsoft.Devices y establecerla en la fuente de nuestra VideoBrush:

public MainPage()
{
    InitializeComponent();

    PhotoCamera camara = new PhotoCamera();
    videoBrush.SetSource(camara);
}

Como se puede ver, el código necesario para hacer funcionar la cámara es realmente sencillo y el resultado espectacular:

image

Si os estáis preguntando como he capturado las imágenes de la cámara funcionando, he usado una herramienta llamada Screen Capture v3 que podéis encontrar en los foros de XDA Developers, exactamente aquí. El único requisito de esta aplicación es que tengáis vuestro dispositivo desbloqueado para desarrollo y os permitirá obtener capturas de pantalla usando el botón de disparo de la cámara.

Ya podemos mostrar la cámara en nuestra aplicación, ¿Que hacemos ahora con ella? Pues lo más sencillo que podemos hacer: sacar una fotografía. En esta acción interviene un evento, el cual nos indicará que tenemos una foto disponible, llamado CaptureImageAvailable y el método CaptureImage del objeto PhotoCamera que llamaremos desde un botón colocado en la interface de usuario:

public partial class MainPage : PhoneApplicationPage
{
    PhotoCamera camara;

    public MainPage()
    {
        InitializeComponent();

        camara = new PhotoCamera();
        camara.CaptureImageAvailable += new EventHandler<ContentReadyEventArgs>(camara_CaptureImageAvailable);
        videoBrush.SetSource(camara);
    }

    void camara_CaptureImageAvailable(object sender, ContentReadyEventArgs e)
    {
        var photo = e.ImageStream;
    }

    private void saveButton_Click(object sender, RoutedEventArgs e)
    {
        camara.CaptureImage();
    }
}

Como podéis ver el código es realmente sencillo: Manejamos el evento CaptureImageAvailable y en el botón saveButton llamamos al método CaptureImage, lo que hace saltar el evento CaptureImageAvailable que nos da un Stream con la fotografía que hemos obtenido. Pero… ¿Y ahora que hacemos con  esta foto? Bien, por defecto Silverlight no incluye métodos para guardar fotografías en la librería multimedia, podríamos usar el almacenamiento aislado para guardar la imagen, pero normalmente querremos que el usuario la tenga accesible desde su galería de imágenes. Para esto, vamos a apoyarnos en él namespace Media de XNA, Microsoft.Xna.Framework.Media, que nos permitirá trabajar con la galería de imágenes del dispositivo:

void camara_CaptureImageAvailable(object sender, ContentReadyEventArgs e)
{
    string photoName = string.Format(@"{0}.jpg", DateTime.Now.Ticks.ToString());

    Dispatcher.BeginInvoke(() =>
    {
        MediaLibrary library = new MediaLibrary();
        library.SavePicture(photoName, e.ImageStream);
    });
}

En este método, podéis ver lo sencillo que es usar el objeto MediaLibrary de XNA para guardar una foto. También hay dos curiosidades: por defecto le he puesto como extensión jpg, esto se debe a que es el formato usado por la cámara para darnos las fotografías. La segunda curiosidad es que estoy usando un Dispatcher para acceder a la librería… esto es así porque el evento CaptureImageAvailable se lanza desde un hilo distinto al de interface de usuario, que es el que usa la MediaLibrary para trabajar, de esta forma tenemos que usar el método SavePicture dentro del Dispatcher para que se ejecute en el hilo de interface de usuario.

Si iniciamos la aplicación y sacamos una foto, podremos ir inmediatamente a verla a la galería de imágenes de Windows Phone. Por defecto, veremos que el resultado no es demasiado espectacular. Es muy posible que la foto salga desenfocada y con poca luz. Es hora de controlar el foco, el flash y la resolución de la cámara.

Esto es algo realmente simple. En el caso del Flash, tenemos una propiedad en el objeto PhotoCamera llamada FlashMode a la que podemos asignar alguno de estos valores:

Valor Modo
On Encendido
Off Apagado
Auto Automático
RedEyeReduction Reducción de ojos rojos (si no está disponible, se usa el Flash normal)

Para establecer el Flash, deberemos tener la cámara totalmente inicializada, para estar seguros de que se ha completado la inicialización, podemos controlar el evento Initialized del objeto PhotoCamera:

public MainPage()
{
    InitializeComponent();
    camara = new PhotoCamera();
    camara.Initialized += new EventHandler<CameraOperationCompletedEventArgs>(camara_Initialized);
    camara.CaptureImageAvailable += new EventHandler<ContentReadyEventArgs>(camara_CaptureImageAvailable);
    videoBrush.SetSource(camara);
}

void camara_Initialized(object sender, CameraOperationCompletedEventArgs e)
{
    camara.FlashMode = FlashMode.On;
}

Una vez hecho esto, siempre que disparemos la cámara el Flash se comportará acorde con lo que hayamos indicado.

El enfoque de cámara es también muy simple. Tenemos un evento AutoFocusCompleted que se lanzará cuando se haya terminado de enfocar y un método Focus() que inicia el enfoque. Vamos a modificar nuestro código para que el botón de disparo llame al método Focus() y en el evento AutoFocusCompleted se realice la captura de pantalla:

public partial class MainPage : PhoneApplicationPage
{
    PhotoCamera camara;

    public MainPage()
    {
        InitializeComponent();
        camara = new PhotoCamera();
        camara.Initialized += new EventHandler<CameraOperationCompletedEventArgs>(camara_Initialized);
        camara.CaptureImageAvailable += new EventHandler<ContentReadyEventArgs>(camara_CaptureImageAvailable);
        camara.AutoFocusCompleted += new EventHandler<CameraOperationCompletedEventArgs>(camara_AutoFocusCompleted);
        videoBrush.SetSource(camara);
    }

    void camara_Initialized(object sender, CameraOperationCompletedEventArgs e)
    {
        camara.FlashMode = FlashMode.On;
    }

    void camara_AutoFocusCompleted(object sender, CameraOperationCompletedEventArgs e)
    {
        camara.CaptureImage();
    }

    void camara_CaptureImageAvailable(object sender, ContentReadyEventArgs e)
    {
        string photoName = string.Format(@"{0}.jpg", DateTime.Now.Ticks.ToString());

        Dispatcher.BeginInvoke(() =>
        {
            MediaLibrary library = new MediaLibrary();
            library.SavePicture(photoName, e.ImageStream);
        });
    }

    private void saveButton_Click(object sender, RoutedEventArgs e)
    {
        camara.Focus();
    }
}

Con este código, la calidad de nuestras fotos es mucho mayor:

image

Ya hemos visto como controlar el Focus y el Flash. Un detalle que nos queda por ver en cuanto a la calidad de las fotos tomadas es la resolución a usar. Esto lo realizaremos mediante dos propiedades: AvailableResolutions, que contiene todas las resoluciones que soporta nuestra cámara, y Resolution, que contiene la resolución activa. Vamos a colocar un listbox en nuestra aplicación que liste todas las resoluciones disponibles y al seleccionar una la activaremos:

void camara_Initialized(object sender, CameraOperationCompletedEventArgs e)
{
    camara.FlashMode = FlashMode.On;
    Dispatcher.BeginInvoke(() =>
    {
        lstResolutions.ItemsSource = camara.AvailableResolutions;
    });
}

private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    camara.Resolution = (Size)lstResolutions.SelectedItem;
}

Y el resultado final es el siguiente:

Screen Capture (11)

Al seleccionar cualquier resolución de la lista, automáticamente veremos como la cámara realiza un pequeño parpadeo y se aplica la nueva resolución. Ahora ya tenemos una aplicación que nos permite sacar fotos con autofocus, flash y diferentes resoluciones. ¿Que más nos falta para desbancar a Nikon del trono de las cámaras digitales? Pues algo muy simple: Poder utilizar el botón físico de disparo que incorporan todos los Windows Phone. Después de todo… para algo está!!

Para hacer esto, tenemos a nuestra disposición en el namespace Microsoft.Devices un objeto llamado CameraButtons que expone tres eventos: ShutterKeyHalfPressed, ShutterKeyPressed y ShutterKeyReleased. Solo necesitamos controlar estos eventos y ejecutar en cada caso el código necesario:

public MainPage()
{
    InitializeComponent();

    CameraButtons.ShutterKeyHalfPressed += new EventHandler(CameraButtons_ShutterKeyHalfPressed);
    CameraButtons.ShutterKeyPressed += new EventHandler(CameraButtons_ShutterKeyPressed);
    CameraButtons.ShutterKeyReleased += new EventHandler(CameraButtons_ShutterKeyReleased);
    camara = new PhotoCamera();
    camara.Initialized += new EventHandler<CameraOperationCompletedEventArgs>(camara_Initialized);
    camara.CaptureImageAvailable += new EventHandler<ContentReadyEventArgs>(camara_CaptureImageAvailable);
    camara.AutoFocusCompleted += new EventHandler<CameraOperationCompletedEventArgs>(camara_AutoFocusCompleted);            
    videoBrush.SetSource(camara);
}

void CameraButtons_ShutterKeyReleased(object sender, EventArgs e)
{
    camara.CancelFocus();
}

void CameraButtons_ShutterKeyPressed(object sender, EventArgs e)
{
    camara.CaptureImage();
}

void CameraButtons_ShutterKeyHalfPressed(object sender, EventArgs e)
{
    camara.Focus();
}

Y con este sencillo código tenemos lista nuestra aplicación para usar el botón físico del teléfono.

Algo más a tener en cuenta antes de terminar: En las especificaciones actuales de Windows Phone no es obligatorio que la cámara tenga autofocus. Para evitar problemas al acceder al método Focus() es conveniente usar la propiedad IsFocusSupported del objeto PhotoCamera primero, y solo llamar a Focus() si está soportado.

Conclusión

En este primer artículo hemos visto como usar el api de cámara propia de Windows Phone, como controlar el comportamiento de la cámara y como usar el botón físico de disparo. Puedes descargarte el código de ejemplo usado en el artículo aquí.

En el próximo artículo veremos como usar el api de webcam de Silverlight 4 desde nuestro dispositivo para grabar audio y video.

Hasta la próxima y Happy Coding!

Un comentario sobre “[Windows Phone 7.5] Acceso a la Cámara (I)”

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *