Checkeo de imágenes (antes de subirlas)

Cuando se quiere que una imagen seleccionada en un formulario pueda ser previsualizada antes del envío del mismo, y poder controlar cosas como tipo, tamaño o dimensiones; podemos encontrarnos con serios problemas. Con este tutorial acompañado de un sencillo ejemplo, podemos ver como conseguirlo.

Limpiando un campo file

Comprobar que un fichero tenga una extensión determinada no es muy difícil con javascript, pero cuando lo tenemos seleccionado y no es correcto, podemos cometer el error de pulsar sobre el botón de envío, debido a ello vamos a proponer un script que permita previsualizar la imagen que seleccionemos, y en el caso de que el fichero seleccionado no tenga la extensión de imagen que nos interese, limpiar el campo file.

La estructura será así:

function checkear_extension(fichero)	{
	// definimos antes los métodos prever() y limpiar()
	(/\.(gif|jpg|png)$/i.test(fichero.value)) ? prever() : limpiar();
}

... la función limpiar():

function limpiar()	{
	f = document.getElementById("archivo");
	nuevoFile = document.createElement("input");
	nuevoFile.id = f.id;
	nuevoFile.type = "file";
	nuevoFile.name = "archivo";
	nuevoFile.value = "";
	nuevoFile.onchange = f.onchange;
	nodoPadre = f.parentNode;
	nodoSiguiente = f.nextSibling;
	nodoPadre.removeChild(f);
	(nodoSiguiente == null) ? nodoPadre.appendChild(nuevoFile):
		nodoPadre.insertBefore(nuevoFile, nodoSiguiente);
}

Como puede verse la forma de limpiar un campo file es algo rebuscada, pero después de hacer muchas pruebas, es la única forma que funciona en los tres navegadores probados. De todos modos, simplemente se podría comprobar la extensión durante la validación del formulario -onsubmit="return /\.(gif|jpg|png)$/i.test(archivo.value)"-.

La visualización

Mostrar una imagen seleccionada desde un campo file podría parecernos cosa fácil, pero por razones de seguridad no se pueden mostrar las imágenes a no ser que estén en la red (tal vez puedan encontrarse truquillos que funcionen en algún navegador, pero no sirven en todos). Solo queda mostrar el método prever que definiremeos dentro del método checkear_extension(), que definitivamente se quedaría de la siguiente manera:

function checkear_extension(fichero)	{

	function prever() {
		var campos = new Array("maxpeso", "maxalto", "maxancho");
		for (i = 0, total = campos.length; i < total; i ++)
			fichero.form[campos[i]].disabled = false;
		actionActual = fichero.form.action;
		targetActual = fichero.form.target;
		fichero.form.action = "previsor.php";
		fichero.form.target = "ver";
		fichero.form.submit();
		for (i = 0, total = campos.length; i < total; i ++)
			fichero.form[campos[i]].disabled = true;
		fichero.form.action = actionActual;
		fichero.form.target = targetActual;
	}

	function no_prever() {
		alert("El fichero seleccionado no es válido...");
		limpiar();
	}

	(/\.(gif|jpg|png)$/i.test(fichero.value)) ? prever() : no_prever();
}

Como comentarios adicionales, solo quedaría matizar que la visualización no es otra cosa que el envío del formulario con la habilitación de los campos que servirán de control, hacia una ventana, que en este caso concreto es un iframe del mismo documento.

El resto de javascript

Lo que quedaría por definir es un sistema validador para evitar el envío del formulario en el caso de no haber seleccionado una imagen válida, y un método que reciba los valores sobre la imagen que se seleccione. Para la primera definición supondremos que la etiqueta form tiene algo de esta guisa:
<form onsubmit="return validar(this)" ... >

function validar(f)	{
	enviar = /\.(gif|jpg|png)$/i.test(f.archivo.value);
	if (!enviar)	alert("seleccione imagen");
	return enviar;
}

Y los datos de la previsualización los recibirá un método que llamaremos datosImagen(), cuyos argumentos serán, el peso, anchura, altura y nº de error (si el error es "0" significa sin error).

function datosImagen(peso, ancho, alto, error)	{
	function mostrar_error()	{
		enviar = false;					
		mensaje = "Ha habido un error (error nº " + error + "):";
		if (error % 2 == 1) // tipo incorrecto
			mensaje += "\nel fichero no es válido";
		error = parseInt(error / 2);
		if (error % 2 == 1) // excede en peso
			mensaje += "\nla imagen pesa mogollón (" + peso + ").";
		error = parseInt(error / 2);
		if (error % 2 == 1) // excede en anchura
			mensaje += "\nla imagen excede en anchura (" + ancho + ").";
		error = parseInt(error / 2);
		if (error % 2 == 1) // excede en altura
			mensaje += "\nla imagen excede en altura (" + alto + ").";
		error = parseInt(error / 2);
		alert (mensaje);
		with (document.forms)	{
			formu.peso.value = 0;
			formu.ancho.value = 0;
			formu.alto.value = 0;
		}
		limpiar();
	}

	if (error == 0)	with (document.forms)	{
		formu.peso.value = peso;
		formu.ancho.value = ancho;
		formu.alto.value = alto;
	}
	else	mostrar_error();
}

Sobre la forma de codificar los errores no voy a hacer comentarios para que este artículo no se haga demasiado extenso.

Gestión desde el servidor

La página destino del formulario durante este sistema de previsualización, debe ser una página web, con una imagen (que en este caso será el fondo de la página), y el contenido de esa imagen será el recibido por la imagen del formulario.

<?php
	session_start();
	$defecto = "webmaster.gif";
	$Ok = isset($_FILES["archivo"]);
	$url = ($Ok) ? $_FILES["archivo"]["tmp_name"] : $defecto;
	list($anchura, $altura, $tipoImagen, $atributos) = getimagesize($url);
	$error = (isset($atributos)) ? 0 : 1;
	$los_tipos = array("gif", "jpg", "png");
	$tipo = ($Ok) ? "image/".$los_tipos[$tipoImagen - 1] : "image/gif";
	$fichero = ($Ok && ($error == 0)) ? $_FILES["archivo"]["name"] : $defecto;
	$tam = filesize($url);
	$OkTam = isset($_POST["maxpeso"]);
	$OkAncho = isset($_POST["maxancho"]);
	$OkAlto = isset($_POST["maxalto"]);
	$maxTam = ($OkTam) ? (int) $_POST["maxpeso"]: 100000;
	$maxAncho = ($OkAncho) ? (int) $_POST["maxancho"]: 640;
	$maxAlto = ($OkAlto) ? (int) $_POST["maxalto"]: 480;
	$error += ($tam <= $maxTam) ? 0 : 2;
	$ancho = ($error == 1) ? 0 : $anchura;
	$alto = ($error == 1) ? 0 : $altura;
	$error += ($ancho <= $maxAncho) ? 0 : 4;
	$error += ($alto <= $maxAlto) ? 0 : 8;
	$datos = ($error == 0) ? $url : $defecto;
	$onload = ($Ok) ? "onload='parent.datosImagen($tam, $ancho, $alto, $error)'": '';
	$datos_imagen = fread(fopen($datos, "rb"), filesize($datos));
	$_SESSION["cont"] = $datos_imagen;
	$_SESSION["tipo"] = ($error == 0) ? $tipo : "image/gif";
?>
<html>
<head>
<style type="text/css" >
html	{
	margin: 0;
	height: 100%;
}
body	{
	height: 100%;
	background-image: url(previendo.php?dato=<?=$fichero;?>);
	background-repeat: no-repeat;
	background-position: center center;
}
</style>
</head>
<body <?=$onload;?>>

</body>
</html>

Resumiendo el código expuesto, diremos que se crea una sesión php y se guarda el código de la imagen y su tipo; además se pone como fondo de la página un fichero (previendo.php) que a pesar de tener parámetro, en la página no se consideran... la razón de esta "curiosidad" es evitar que la imagen sea mostrada por los navegadores desde la memoria caché. Otra curiosidad es la definición en la etiqueta body (sus estilos se ven en la sección style de la cabecera del documento) del evento load (onload), donde se envían datos a la ventana "parent", con información de la imagen y un código de error, que de ser distinto de 0 en la página "padre" mostrará una alerta con la descripción del mismo.

Para finalizar mostraremos el contenido del fichero previendo.php, que como se puede adivinar, tan solo envía el contenido de la imagen a la consola:

<?php
session_start();
$url = ($_SESSION["cont"] == "")
	? fread(fopen("webmaster.gif", "rb"), filesize("webmaster.gif"))
	: $_SESSION["cont"];
$tip = ($_SESSION["tipo"] == "")
	? "image/gif"
	: $_SESSION["tipo"];
header("Content-type: $tip");
echo $url;
session_destroy();
?>

Solo nos queda mostrar este código en funcionamiento; para ello hemos creado el formulario que se puede ver en el siguiente enlace: formulario de ejemplo.

Enlaces interesantes sobre el tema

Este tema es muy recurrente en los Foros del web: