bases de datos

PHP: tomar foto con cámara y guardar en MySQL

En este post te mostraré cómo tomar una foto con la cámara web usando JavaScript; para más tarde enviarla a PHP y guardarla en una base de datos.

Recuerda que ya anteriormente vimos cómo hacer lo mismo pero guardando la foto en el disco duro, esto no es más que una modificación a lo que aparece en ese post.

Recomiendo encarecidamente que leas el post que menciono, pues aquí solo cambia la parte del guardado de la foto.

Definiendo la estructura de la base de datos

Lo primero que haremos será definir la estructura de la tabla. He optado por guardar la foto en base64 usando un tipo de dato LONGBLOB que es el BLOB con mayor capacidad.

Para efectos de simplicidad el esquema es así:

CREATE TABLE fotos(id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, foto LONGBLOB NOT NULL); 

Como ves simplemente tiene un id que es su llave primaria, y la foto.

Tomando la foto

El envío de la foto queda igual que en lo anterior. Lo dejaré aquí de igual manera. Tenemos el index.html que muestra una visualización de la cámara, así como un botón para tomar la foto:

<!DOCTYPE html>
<html lang="es">

<head>
 <!--
  Tomar una fotografía y guardarla en un archivo v3
     @date 2018-10-22
     @author parzibyte
     @web parzibyte.me/blog
 -->
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 <title>Tomar foto </title>
 <style>
  @media only screen and (max-width: 700px) {
   video {
    max-width: 100%;
   }
  }
 </style>
</head>

<body>
 <h1>Tomando foto</h1>
 
 <h1>Selecciona un dispositivo</h1>
 <div>
  <select name="listaDeDispositivos" id="listaDeDispositivos"></select>
  <button id="boton">Tomar foto</button>
  <p id="estado"></p>
 </div>
 <br>
 <video muted="muted" id="video"></video>
 <canvas id="canvas" style="display: none;"></canvas>
</body>
<script src="script.js"></script>

</html>

Después se maneja todo con un script de JavaScript:

/*
    Tomar una fotografía y guardarla en un archivo v3
    @date 2018-10-22
    @author parzibyte
    @web parzibyte.me/blog
*/const tieneSoporteUserMedia = () =>
    !!(navigator.getUserMedia || (navigator.mozGetUserMedia || navigator.mediaDevices.getUserMedia) || navigator.webkitGetUserMedia || navigator.msGetUserMedia)
const _getUserMedia = (...arguments) =>
    (navigator.getUserMedia || (navigator.mozGetUserMedia || navigator.mediaDevices.getUserMedia) || navigator.webkitGetUserMedia || navigator.msGetUserMedia).apply(navigator, arguments);

// Declaramos elementos del DOM
const $video = document.querySelector("#video"),
    $canvas = document.querySelector("#canvas"),
    $estado = document.querySelector("#estado"),
    $boton = document.querySelector("#boton"),
    $listaDeDispositivos = document.querySelector("#listaDeDispositivos");

const limpiarSelect = () => {
    for (let x = $listaDeDispositivos.options.length - 1; x >= 0; x--)
        $listaDeDispositivos.remove(x);
};
const obtenerDispositivos = () => navigator
    .mediaDevices
    .enumerateDevices();

// La función que es llamada después de que ya se dieron los permisos
// Lo que hace es llenar el select con los dispositivos obtenidos
const llenarSelectConDispositivosDisponibles = () => {

    limpiarSelect();
    obtenerDispositivos()
        .then(dispositivos => {
            const dispositivosDeVideo = [];
            dispositivos.forEach(dispositivo => {
                const tipo = dispositivo.kind;
                if (tipo === "videoinput") {
                    dispositivosDeVideo.push(dispositivo);
                }
            });

            // Vemos si encontramos algún dispositivo, y en caso de que si, entonces llamamos a la función
            if (dispositivosDeVideo.length > 0) {
                // Llenar el select
                dispositivosDeVideo.forEach(dispositivo => {
                    const option = document.createElement('option');
                    option.value = dispositivo.deviceId;
                    option.text = dispositivo.label;
                    $listaDeDispositivos.appendChild(option);
                });
            }
        });
}

(function() {
    // Comenzamos viendo si tiene soporte, si no, nos detenemos
    if (!tieneSoporteUserMedia()) {
        alert("Lo siento. Tu navegador no soporta esta característica");
        $estado.innerHTML = "Parece que tu navegador no soporta esta característica. Intenta actualizarlo.";
        return;
    }
    //Aquí guardaremos el stream globalmente
    let stream;


    // Comenzamos pidiendo los dispositivos
    obtenerDispositivos()
        .then(dispositivos => {
            // Vamos a filtrarlos y guardar aquí los de vídeo
            const dispositivosDeVideo = [];

            // Recorrer y filtrar
            dispositivos.forEach(function(dispositivo) {
                const tipo = dispositivo.kind;
                if (tipo === "videoinput") {
                    dispositivosDeVideo.push(dispositivo);
                }
            });

            // Vemos si encontramos algún dispositivo, y en caso de que si, entonces llamamos a la función
            // y le pasamos el id de dispositivo
            if (dispositivosDeVideo.length > 0) {
                // Mostrar stream con el ID del primer dispositivo, luego el usuario puede cambiar
                mostrarStream(dispositivosDeVideo[0].deviceId);
            }
        });



    const mostrarStream = idDeDispositivo => {
        _getUserMedia({
                video: {
                    // Justo aquí indicamos cuál dispositivo usar
                    deviceId: idDeDispositivo,
                }
            },
            (streamObtenido) => {
                // Aquí ya tenemos permisos, ahora sí llenamos el select,
                // pues si no, no nos daría el nombre de los dispositivos
                llenarSelectConDispositivosDisponibles();

                // Escuchar cuando seleccionen otra opción y entonces llamar a esta función
                $listaDeDispositivos.onchange = () => {
                    // Detener el stream
                    if (stream) {
                        stream.getTracks().forEach(function(track) {
                            track.stop();
                        });
                    }
                    // Mostrar el nuevo stream con el dispositivo seleccionado
                    mostrarStream($listaDeDispositivos.value);
                }

                // Simple asignación
                stream = streamObtenido;

                // Mandamos el stream de la cámara al elemento de vídeo
                $video.srcObject = stream;
                $video.play();

                //Escuchar el click del botón para tomar la foto
                //Escuchar el click del botón para tomar la foto
                $boton.addEventListener("click", function() {

                    //Pausar reproducción
                    $video.pause();

                    //Obtener contexto del canvas y dibujar sobre él
                    let contexto = $canvas.getContext("2d");
                    $canvas.width = $video.videoWidth;
                    $canvas.height = $video.videoHeight;
                    contexto.drawImage($video, 0, 0, $canvas.width, $canvas.height);

                    let foto = $canvas.toDataURL(); //Esta es la foto, en base 64
                    $estado.innerHTML = "Enviando foto. Por favor, espera...";
                    fetch("./guardar_foto.php", {
                            method: "POST",
                            body: encodeURIComponent(foto),
                            headers: {
                                "Content-type": "application/x-www-form-urlencoded",
                            }
                        })
                        .then(resultado => {
                            // A los datos los decodificamos como texto plano
                            return resultado.text()
                        })
                        .then(idFoto => {
                            // idFoto trae el id de la foto
                            console.log("La foto fue enviada correctamente");
                            $estado.innerHTML = `Foto guardada con éxito. Puedes verla <a target='_blank' href='./ver.php?id=${idFoto}'> aquí</a>`;
                        })

                    //Reanudar reproducción
                    $video.play();
                });
            }, (error) => {
                console.log("Permiso denegado o error: ", error);
                $estado.innerHTML = "No se puede acceder a la cámara, o no diste permiso.";
            });
    }
})();

La base de datos

He tomado el archivo de conexión de mi post de PDO y PHP con MySQL. Se ve así:

<?php
$contraseña = "";
$usuario = "root";
$nombre_base_de_datos = "fotos";
try{
    $base_de_datos = new PDO('mysql:host=localhost;dbname=' . $nombre_base_de_datos, $usuario, $contraseña);
    $base_de_datos->query("set names utf8;");
    $base_de_datos->setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE);
    $base_de_datos->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $base_de_datos->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
}catch(Exception $e){
 echo "Ocurrió algo con la base de datos: " . $e->getMessage();
}

Este archivo lo debemos incluir en donde queramos conectarnos a la base de datos. Recuerda cambiar tus credenciales.

Recibiendo y guardando la foto en base de datos

Ahora veamos el archivo de PHP que recibe la foto y la guarda en MySQL. Queda así:

<?php
/*
    Tomar una fotografía y guardarla en MySQL
    @date @date 2020-04-06
    @author parzibyte
    @web parzibyte.me/blog
*/
$imagenCodificada = file_get_contents("php://input"); //Obtener la imagen
if(strlen($imagenCodificada) <= 0) exit("No se recibió ninguna imagen");
//La imagen traerá al inicio data:image/png;base64, cosa que debemos remover

//Venía en base64 pero sólo la codificamos así para que viajara por la red, ahora la decodificamos y
//todo el contenido lo guardamos en un archivo
$imagenCodificadaLimpia = str_replace("data:image/png;base64,", "", urldecode($imagenCodificada));
$imagenDecodificada = base64_decode($imagenCodificadaLimpia);

include_once "bd.php";
$sentencia = $base_de_datos->prepare("INSERT INTO fotos(foto) VALUES(?)");
$sentencia->execute([$imagenCodificadaLimpia]);
$id = $base_de_datos->lastInsertId();

//Terminar y regresar el nombre de la foto
exit($id);
?>

Lo único que hacemos es simplemente guardar la cadena en base64 dentro de la base de datos, quitándole el prefijo de data:image/png;base64,

Usamos sentencias preparadas (prepare) para evitar inyecciones SQL.

Mostrando foto

Ahora veamos cómo mostrar esta foto. Debido a que ya la tenemos en base64 podemos mostrarla como src en una etiqueta img:

<?php
include_once "bd.php";
$id = $_GET["id"];
if (empty($id)) {
    exit("No existe el parámetro id en la URL");
}

$sentencia = $base_de_datos->prepare("SELECT foto FROM fotos WHERE id = ?");
$sentencia->execute([$id]);
$foto = $sentencia->fetchObject();
if (!$foto) {
    exit("No hay foto con ese ID");
}

?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Ver foto</title>
</head>
<body>
    <h1>Foto:</h1>
    <img src="data:image/png;base64,<?php echo $foto->foto; ?>">
</body>
</html>

De este modo la foto se va a mostrar al visitar ver.php y pasarle el ID.

Poniendo todo junto

El código fuente de todo este proyecto lo encuentras en GitHub. Recuerda que se puede acceder a la cámara solo si usas localhost o https.

Mi recomendación

Yo te recomiendo que no guardes la foto en la base de datos, sino en el disco duro. Y que en la base de datos guardes el nombre de la foto o un id de la misma.

El ejemplo aquí mostrado solo es por si necesitas saber cómo guardar la foto en MySQL.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.
parzibyte

Programador freelancer listo para trabajar contigo. Aplicaciones web, móviles y de escritorio. PHP, Java, Go, Python, JavaScript, Kotlin y más :) https://parzibyte.me/blog/software-creado-por-parzibyte/

Ver comentarios

  • Hola buenas tardes, tu codigo funciona perfecto, me ayudo mucho en lo que estaba haciendo, pero mi pregunta ahora es, como se puede hacer para tomar 3 fotos diferentes y enviarlas a la BD.

    • Por supuesto, estaré encantado de ayudarle más a fondo. Ofrezco servicios de consultoría personalizados para resolver problemas específicos. Si está interesado, envíeme un mensaje a https://parzibyte.me/#contacto y podemos conversar sobre cómo puedo ayudarle.

  • Buenas noches, un favor, como hago esto: Yo te recomiendo que no guardes la foto en la base de datos, sino en el disco duro. Y que en la base de datos guardes el nombre de la foto o un id de la misma.

  • Buen Dia, Baje los codigo, cree la table fotos, ejecuta index.html y se detiene en el mensaje de enviando foto , por favor espera.

    Revise la consola y aparece que CORS ha bloqueado, la aplicacion, instale la extencion y sigue igual.
    lo ejecute en windows chrome y en android

  • Excelente código, me ayuda mucho en lo que busco para implementar. Ahora como podría adjuntar un formulario a ese index.html para enviarlo y capturarlo en el .js ? necesito más objetos para luego enviarlo al guardar_foto.php?

  • Excelente blog y excelentes las ideas y soluciones.
    Con el uso de la camara lmentablemente la privacidad de los iphone no permite el acceso a la camara de parte del navegador.
    Alguna sugerencia de como poder forzar el mensaje de autorización?

Entradas recientes

Creador de credenciales web – Aplicación gratuita

Hoy te voy a presentar un creador de credenciales que acabo de programar y que…

1 semana hace

Desplegar PWA creada con Vue 3, Vite y SQLite3 en Apache

Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…

2 semanas hace

Arquitectura para wasm con Go, Vue 3, Pinia y Vite

En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…

2 semanas hace

Vue 3 y Vite: crear PWA (Progressive Web App)

En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…

2 semanas hace

Errores de Comlink y algunas soluciones

Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…

2 semanas hace

Esperar promesa para inicializar Store de Pinia con Vue 3

En este artículo te voy a enseñar cómo usar un "top level await" esperando a…

2 semanas hace

Esta web usa cookies.