Cómo tomar fotos y controlar la configuración de la cámara

Miguel Casas-Sánchez
Francisco Beaufort
François Beaufort

Image Capture es una API que captura imágenes estáticas y configura el hardware de la cámara. Esta API está disponible en Chrome 59 en Android y computadoras de escritorio. También publicamos una biblioteca de polyfills ImageCapture.

La API permite controlar las funciones de la cámara, como el zoom, el brillo, el contraste, la escala ISO y el balance de blancos. Lo mejor de todo es que la captura de imágenes te permite acceder a las capacidades de resolución completa de cualquier cámara o cámara web de un dispositivo disponible. En las técnicas anteriores para tomar fotos en la Web, se utilizaban instantáneas de video, que tienen una resolución menor que la disponible para imágenes estáticas.

Un objeto ImageCapture se construye con un MediaStreamTrack como origen. Luego, la API tiene dos métodos de captura, takePhoto() y grabFrame(), y formas de recuperar las capacidades y los parámetros de configuración de la cámara, y de cambiar esos parámetros de configuración.

Construcción

La API de captura de imágenes obtiene acceso a una cámara a través de un MediaStreamTrack obtenido de getUserMedia():

navigator.mediaDevices.getUserMedia({video: true})
    .then(gotMedia)
    .catch(error => console.error('getUserMedia() error:', error));

function gotMedia(mediaStream) {
    const mediaStreamTrack = mediaStream.getVideoTracks()[0];
    const imageCapture = new ImageCapture(mediaStreamTrack);
    console.log(imageCapture);
}

Puedes probar este código desde la consola de Herramientas para desarrolladores.

Captura

La captura se puede hacer de dos maneras: en fotograma completo e instantánea rápida. takePhoto() muestra un Blob, el resultado de una sola exposición fotográfica, que se puede descargar, almacenar en el navegador o mostrar en un elemento <img>. Este método utiliza la resolución de cámara fotográfica más alta disponible. Por ejemplo:

const img = document.querySelector('img');
// ...
imageCapture.takePhoto()
    .then(blob => {
    img.src = URL.createObjectURL(blob);
    img.onload = () => { URL.revokeObjectURL(this.src); }
    })
    .catch(error => console.error('takePhoto() error:', error));

grabFrame() muestra un objeto ImageBitmap, una instantánea de un video en vivo que, por ejemplo, podría dibujarse en un <canvas> y, luego, procesarse posteriormente para cambiar de forma selectiva los valores de color. Ten en cuenta que ImageBitmap solo tendrá la resolución de la fuente de video, que suele ser más baja que las capacidades de imagen fija de la cámara. Por ejemplo:

const canvas = document.querySelector('canvas');
// ...
imageCapture.grabFrame()
    .then(imageBitmap => {
    canvas.width = imageBitmap.width;
    canvas.height = imageBitmap.height;
    canvas.getContext('2d').drawImage(imageBitmap, 0, 0);
    })
    .catch(error => console.error('grabFrame() error:', error));

Funciones y configuración

Hay varias formas de manipular la configuración de captura, según si los cambios se reflejarán en el MediaStreamTrack o si solo se podrán ver después de takePhoto(). Por ejemplo, un cambio en el nivel de zoom se propaga de inmediato al MediaStreamTrack, mientras que la reducción de ojos rojos, cuando se configura, solo se aplica cuando se toma la foto.

Las capacidades y la configuración de la cámara "en vivo" se manipulan a través de la vista previa MediaStreamTrack: MediaStreamTrack.getCapabilities() muestra un diccionario MediaTrackCapabilities con las capacidades concretas admitidas y los rangos o valores permitidos, p.ej., el rango de zoom o los modos de balance de blancos admitidos. En consecuencia, MediaStreamTrack.getSettings() muestra un MediaTrackSettings con la configuración actual concreta. El zoom, el brillo y el modo linterna pertenecen a esta categoría, por ejemplo:

var zoomSlider = document.querySelector('input[type=range]');
// ...
const capabilities = mediaStreamTrack.getCapabilities();
const settings = mediaStreamTrack.getSettings();
if (capabilities.zoom) {
    zoomSlider.min = capabilities.zoom.min;
    zoomSlider.max = capabilities.zoom.max;
    zoomSlider.step = capabilities.zoom.step;
    zoomSlider.value = settings.zoom;
}

Las capacidades y la configuración de la cámara "no publicadas" se manipulan mediante el objeto ImageCapture: ImageCapture.getPhotoCapabilities() muestra un objeto PhotoCapabilities que proporciona acceso a las funciones de la cámara disponibles para el estado "No publicado". En consecuencia, a partir de Chrome 61, ImageCapture.getPhotoSettings() muestra un objeto PhotoSettings con la configuración actual concreta. La resolución de la foto, la reducción de ojos rojos y el modo de flash (excepto la linterna) pertenecen a esta sección, por ejemplo:

var widthSlider = document.querySelector('input[type=range]');
// ...
imageCapture.getPhotoCapabilities()
    .then(function(photoCapabilities) {
    widthSlider.min = photoCapabilities.imageWidth.min;
    widthSlider.max = photoCapabilities.imageWidth.max;
    widthSlider.step = photoCapabilities.imageWidth.step;
    return imageCapture.getPhotoSettings();
    })
    .then(function(photoSettings) {
    widthSlider.value = photoSettings.imageWidth;
    })
    .catch(error => console.error('Error getting camera capabilities and settings:', error));

Configuración

La configuración de la cámara "en vivo" se puede establecer mediante las restricciones de applyConstraints() de la vista previa de MediaStreamTrack, por ejemplo:

var zoomSlider = document.querySelector('input[type=range]');

mediaStreamTrack.applyConstraints({ advanced: [{ zoom: zoomSlider.value }]})
    .catch(error => console.error('Uh, oh, applyConstraints() error:', error));

La configuración de la cámara "No publicada" se establece con el diccionario opcional PhotoSettings de takePhoto(), por ejemplo:

var widthSlider = document.querySelector('input[type=range]');
imageCapture.takePhoto({ imageWidth : widthSlider.value })
    .then(blob => {
    img.src = URL.createObjectURL(blob);
    img.onload = () => { URL.revokeObjectURL(this.src); }
    })
    .catch(error => console.error('Uh, oh, takePhoto() error:', error));

Capacidades de la cámara

Si ejecutas el código anterior, notarás una diferencia de dimensiones entre los resultados de grabFrame() y takePhoto().

El método takePhoto() otorga acceso a la resolución máxima de la cámara.

grabFrame() solo toma el siguiente VideoFrame disponible en el MediaStreamTrack dentro del proceso del renderizador, mientras que takePhoto() interrumpe el MediaStream, reconfigura la cámara, toma la foto (por lo general, en un formato comprimido, por lo tanto, Blob) y, luego, reanuda la MediaStreamTrack. En esencia, esto significa que takePhoto() otorga acceso a las capacidades completas de resolución de imagen fija de la cámara. Anteriormente, solo era posible "tomar una foto" llamando a drawImage() en un elemento canvas con un video como fuente (como se ve aquí).

Para obtener más información, consulta la sección README.md.

En esta demostración, las dimensiones <canvas> se establecen en la resolución de la transmisión de video, mientras que el tamaño natural de <img> es la resolución máxima de imágenes estáticas de la cámara. CSS, por supuesto, se usa para establecer el tamaño de visualización de ambos.

Se puede obtener y configurar el rango completo de resoluciones de cámara disponibles para imágenes estáticas mediante los valores de MediaSettingsRange para PhotoCapabilities.imageHeight y imageWidth. Ten en cuenta que las restricciones mínimas y máximas de ancho y altura de getUserMedia() se aplican a los videos y, como se explicó, pueden diferir de las capacidades de la cámara para imágenes estáticas. En otras palabras, es posible que no puedas acceder a las capacidades de resolución completa del dispositivo cuando guardes contenido de getUserMedia() en un lienzo. En la demostración de la restricción de resolución de WebRTC, se muestra cómo establecer restricciones de getUserMedia() para la resolución.

¿Algo más?

  • La API de Shape Detection funciona bien con la captura de imágenes: se puede llamar a grabFrame() de forma repetida para enviar ImageBitmap a un FaceDetector o BarcodeDetector. Obtén más información sobre la API en esta entrada de blog de Paul Kinlan.

  • Se puede acceder al Flash de la cámara (luz del dispositivo) a través de FillLightMode en PhotoCapabilities, pero puedes encontrar el modo linterna (parpadeo constante) en MediaTrackCapabilities.

Demostraciones y muestras de código

Asistencia

  • Chrome 59 en Android y computadoras de escritorio.
  • Chrome Canary en Android y computadoras de escritorio anteriores a la versión 59 con las funciones de la plataforma web experimental habilitadas.

Más información