Plugin ESC POS v3

Cámara a impresora térmica – Aplicación web

Hoy te voy a enseñar una aplicación web (programada con JavaScript en el lado del cliente) para imprimir fotos en una impresora térmica. Las fotos que se van a imprimir serán tomadas al instante con la cámara web o cámara integrada.

Foto de cámara en impresora térmica ESC POS

Gracias a esta webapp vas a poder tomar una foto de la cámara y enviarla a una impresora térmica aplicando dithering para mejorar su calidad.

Puedes acceder a la demostración ya mismo: cámara a impresora térmica.

El proceso será automático, solo debes conectar tu impresora térmica por USB, abrir la página web, seleccionar tu cámara y presionar el botón para tomar una fotografía e imprimirla en una impresora térmica.

Tomar foto e imprimir al instante

Si no quieres ver los detalles técnicos entonces puedes visitar la siguiente página para tomar fotos con tu cámara e imprimirlas conservando varios detalles: cámara a impresora térmica.

Acceder a la cámara con JavaScript

Ya tengo varios tutoriales en mi blog sobre cómo tomar una foto con la cámara desde el navegador web usando JS. Tengo una guía para tomar la foto y descargarla, otra para enviarla a PHP y otra para enviarla a Telegram.

El código importante es el siguiente que accede al stream de la cámara y lo coloca en un elemento HTML video para mostrárselo al usuario.

async function iniciarStream() {
    // 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;
    }
    const mostrarStream = idDeDispositivo => {
        _getUserMedia({
            video: {
                // Justo aquí indicamos cuál dispositivo usar
                deviceId: idDeDispositivo,
            }
        },
            async (streamObtenido) => {
                // Aquí ya tenemos permisos, ahora sí llenamos el select,
                // pues si no, no nos daría el nombre de los dispositivos
                if ($listaDeDispositivos.length <= 0) {
                    llenarSelectConDispositivosDisponibles($listaDeDispositivos);
                }

                // 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;
                // Refrescar el canvas
                await $video.play();
                canvasFueraDePantalla = new OffscreenCanvas($video.videoWidth, $video.videoHeight);
                contextoCanvas = canvasFueraDePantalla.getContext("2d");

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

    // Comenzamos pidiendo los dispositivos
    const dispositivos = await navigator.mediaDevices.enumerateDevices();
    // 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);
    }
}

Tomar foto

Para tomar la foto y enviarla a la impresora térmica es necesario pausar el vídeo, pintar el fotograma en un canvas (que será una instancia de OffscreenCanvas) y convertir ese canvas a imagen en base64:

const tomarFoto = async () => {
    //Pausar reproducción
    $video.pause();
    canvasFueraDePantalla.width = $video.videoWidth;
    canvasFueraDePantalla.height = $video.videoHeight;
    //Obtener contexto del canvas y dibujar sobre él
    contextoCanvas.drawImage($video, 0, 0, canvasFueraDePantalla.width, canvasFueraDePantalla.height);
    //Reanudar reproducción
    await $video.play();
    const blob = await canvasFueraDePantalla.convertToBlob();
    const reader = new FileReader();
    reader.onloadend = async () => {
        await imprimirFoto($impresoras.value, reader.result, parseInt($algoritmo.value), $licencia.value);
    }
    reader.onerror = () => { }
    reader.readAsDataURL(blob);
}

Imprimir

Ahora que ya tenemos la foto procederemos a imprimirla en una impresora térmica. Para ello veamos la siguiente función llamada imprimirFoto que recibe el nombre de la impresora térmica, la imagen en base64, el algoritmo de conversión de imagen y un serial opcional.

Lo importante está en el arreglo operaciones que contiene la operación ESC POS que se va a enviar a la impresora térmica. Como tenemos la imagen codificada en base64 invocamos a ImprimirImagenEnBase64.

Los argumentos de la operación son la cadena en base64, el ancho máximo, el algoritmo y una bandera indicando si se debería aplicar dithering antes de imprimir la imagen.

const imprimirFoto = async (nombreImpresora, fotoEnBase64, algoritmo, licencia) => {
    const payload = {
        "serial": licencia,
        "nombreImpresora": nombreImpresora,
        "operaciones": [
            {
                "nombre": "ImprimirImagenEnBase64",
                "argumentos": [
                    fotoEnBase64,
                    380,
                    algoritmo,
                    true
                ]
            },
        ]
    };
    const httpResponse = await fetch("http://localhost:8000/imprimir",
        {
            method: "POST",
            body: JSON.stringify(payload),
        });

    const jsonResponse = await httpResponse.json();
    if (jsonResponse.ok) {
        // Everything is ok
        console.log("Printed successfully");
    } else {
        // Error message is on message property
        console.error(jsonResponse.message)
    }
}

Y al hacer esta petición, la foto se habrá impreso en la impresora térmica con la calidad máxima posible. En resumen es:

  1. Obtener stream de cámara y colocarlo en un elemento HTML de tipo <video>
  2. Al tomar la foto dibujar el fotograma actual en un OffscreenCanvas
  3. Convertir el OffscreenCanvas a base64
  4. Enviar esa foto en base64 a la API de la impresora térmica aplicando dithering para mejorar la calidad de la imagen

De este modo puedes imprimir fotos instantáneas desde tu navegador web.

Código fuente

Te he mostrado el código más importante, pero puedes acceder al repositorio completo en GitHub. También puedes acceder a la aplicación web para tomar foto con la cámara e imprimirla en una impresora térmica.

Configurar impresora y descargar servidor HTTP a ESC POS

Para que el código funcione necesitas el servidor local de la API ESC POS ejecutándose y que tu impresora esté compartida. Por favor, sigue los pasos que aparecen en la página previamente enlazada, ya que ahí está la API unificada.

La documentación completa está en: https://parzibyte.me/http-esc-pos-desktop-docs/es/

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/

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.