unafotopara.lanshor.com

El otro día pensaba en lo que me gustaría tener un porta-fotos digital en el que, en vez de añadir yo manualmente las fotos que quisiera ver… apareciesen las que amigos y familiares me envían desde España (o desde dónde estén).

Puede parecer un poco extraño… pero me gustaba la idea de no saber qué encontrarme cada vez que lo mirase. Al menos es bastante más divertido!!

Obviamente, tal cosa no existe, pero… como reza mi credo: si quieres algo que no existe o no puedes alcanzar… ¡créalo tú! Además acababa de recibir una Raspberry Pi 3… ocurrírseme usarla para convertir mi anhelo en un proyecto real fue como montar un puzzle de dos piezas.

20160413_125252_HDR

No tuve quedarle muchas vueltas para convencerme: dos días después tenía una pantalla de 7 pulgadas, un poco de metacrilato para las patas y la sujeción trasera de la pantalla, un pequeño adaptador de corriente…

20160415_114322_HDR

Una vez todo montado quedó mucho mejor de lo que esperaba 😀

20160416_084758_HDR

Ya sólo quedaba escribir el software, para lo que básicamente he aprovechado los ratos libres de este fin de semana. Así que hoy domingo… tras unas cuentas horas de código…

20160417_173822_HDR

¡Vualà! \(^.^)/

Podéis enviarme fotos que aparecerán automáticamente en el porta-fotos desde http://unafotopara.lanshor.com. Las fotos pueden enviarse directas de forma anónima o con mensaje y autor (explora los botones de colores de debajo de la página). Para mandar una foto arrástrala a la imagen del porta-fotos o haz clic en él y selecciona el archivo.

De momento estoy muy contento con el resultado 🙂 Quizá lo vaya mejorando y añadiendo más features conforme me apetezca.

No he hablado de ninguna cuestión técnica en esta entrada, pero si tenéis alguna pregunta no dudéis en comentar. Para todo lo demás…

Leer más

PHP: Ejemplo de mal lenguaje

Supongamos que tenemos 5 variables a, b, c, d y e. Y que todas y cada una de las siguientes evaluaciones devuelven TRUE.

$a === $b
$b === $c
$c === $d
$d === $e

Además si hacemos lo siguiente:

var_dump($a);
var_dump($b);
var_dump($c);
var_dump($d);
var_dump($e);

Obtenemos exactamente la misma definición, repetida 5 veces, por salida estándar.

Llegados a este punto, hemos analizado -todo lo que el lenguaje nos permite- nuestras 5 variables. Todas parecen ser idénticas, no hay ninguna propiedad en ellas que podamos medir y que nos permitan diferenciar unas de otras.

Sin embargo, al hacer lo siguiente:

$b = new stdClass;

Al volver a repetir las evaluaciones iniciales el resultado es el siguiente:

$a === $b //FALSE
$b === $c //TRUE
$c === $d //TRUE
$d === $e //TRUE

En conclusión: 5 entidades que según el lenguaje son idénticas y por lo tanto, deben comportarse de la misma forma, tienen un comportamiento diferente; debido a propiedades internas imposibles de discernir en la capa de código.

Este es un claro ejemplo de pésimo diseño a nivel de lenguaje y, por supuesto, una fuente potencial de fuertes dolores de cabeza para cualquier programador.

Como pequeño ejercicio: ¿alguien sabe cómo hay que inicializar las 5 variables para que se comporten de la forma descrita?

Leer más

Acelerar ‘scp’ un 800% con tar

El comando ‘scp’ es sin duda la opción más cómoda y útil a la hora de copiar archivos entre distintas máquinas. Es seguro (funciona a través de ssh) y versátil; elegimos origen y destino (máquina, usuario y ruta para cada extremo (por omisión los ídem locales)). Y a esperar.

A esperar; sobre todo porque por cada archivo que copiemos se abrirá y se cerrará una nueva conexión, algo en lo que ssh es especialmente lento (al estilo de FTP). Si copiamos archivos muy grandes no tenemos problema, pero cuando copiamos directorios con cientos, miles, o decenas o centenas de miles de archivos podemos llegar a tardar varios días en realizar una copia de unos pocos GiBs (la inmensa mayor parte del tiempo perdido en abrir y cerrar conexiones). El caso del típico directorio con infinitos pequeños archivos de texto (emails, información de sesiones, carpetas como /etc, etc…).

La solución más común es hacer un tar (o un zip; aunque este último no conservará usuarios, grupos y permisos) y copiar el archivo empaquetado, pero esto se vuelve poco práctico porque nos obliga como mínimo a entrar en la máquina de destino a descomprimir el archivo copiado si el origen es local; o entrar en la máquina de origen para empaquetar si el origen es remoto.

¿La solución? Empaquetar y desempaquetar al vuelo los archivos que queremos copiar, de la siguiente forma:

Copiar de local a remoto

tar cfz - /origen | ssh usuario@host tar zxvf - -C /destino

Copiar de remoto a local

ssh usuario@host "tar cfz - /origen" | tar zxvf - -C /destino

 

Donde:

c: Crea un nuevo “archivo” (paquete en realidad) al que añadir elementos del sistema de ficheros.
f: El siguiente argumento indica la ruta y nombre del “archivo”.
z: Comprime usando gzip (opcional). Vale la pena gastar un extra CPU, los archivos de texto (lo habitual) se comprimen mucho y muy rápido.
x: Extrae los archivos de un “archivo” tar.
-C: Cambia al directorio indicado a continuación (debe existir).
: El guión indica que el “archivo” de destino es la salida o la entrada estándar (dependiendo de si empaquetamos o desempaquetamos). Dado que en sistemas tipo unix todo es un “archivo”, no hay nada de extraño en hacer esto.

La mejora de velocidad que he medido en mis pruebas ha sido de hasta el 800% (que se dice pronto). Pero seguro que vosotros podéis conseguir aún más (más cuantos más archivos y más pequeños estéis copiando).

¿Alguien se atreve a montar la versión que copia entre máquinas remotas directamente?

Leer más

BananaDB: MySQL desde PHP

Como mucha gente me la está pidiendo y va pasando de mano en mano rulando por ahí, he decidido liberar y subir a GitHub una pequeña clase que hice hace unos meses para manejar el driver MySQLi de PHP. Se llama BananaDB (porque es un nombre que mola, y porque las siglas BDB pueden decirse en al menos dos idiomas (Banana DataBases o Bases de Datos Banana).

BananaDB

Estaréis pensando que librerías con el mismo propósito hay muchas, y es cierto. Pero me lancé a escribir la mía propia (sin muchas ganas, no me gusta rehacer la rueda si no es por diversión) porque ninguna de cuantas he probado cumple con los requisitos que yo considero esenciales. Todas ensucian mucho el código y algunas no ofrecen características esenciales para mí como las sentencias preparadas o la posibilidad de acceder al driver directamente. Esto no sólo ocurre con las librerías especializadas, cuando veo las clases controladoras de Bases de Datos que usan los grandes CMS de hoy en día es peor; y no sé si reír o llorar.

No tenía mucho tiempo para hacerla, así que es bastante sencilla. Faltan algunas features por implementar (nada que yo haya llegado a necesitar, claro). Seguramente tenga errores [ pero ahora podéis mandarme un pull request y corregirlos 🙂 o incluso completar las features que faltan 🙂 🙂 ]. La publico tal cual está. Sin testear demasiado. Pero en cualquier caso, creo que es mejor que lo que hay…

Mis metas esenciales eran (mejor explicadas, junto a otras colaterales, en la página de GitHub):

  • Escribir sentencias SQL directamente: Sin métodos, ni palabras que mezclasen las consultas. De forma que el código se mantuviera legible.
  • Poder usar sentencias preparadas (prepared statements) de forma transparente.

Repositorio y descarga

Tenéis la documentación y el código en GitHub.
O si lo preferís, un enlace al código directamente.

Sobre el desarrollo

Escribiendo la clase ha habido algunos retos interesantes que no me esperaba encontrar.

Uno muy sencillo de resolver es la concatenación de métodos en cierto orden y formando una cadena infinita (and()->and()->or()->…) y la recepción de parámetros en múltiples formatos. Creando de esta forma la sintaxis básica de uso de la clase. Tal que así:

$name=$bd->select("name")->from("fruits")->
      where("price > 100")->and("id < ", $max_id)->
      or("color = ", $color)->and ...

La llamada a un método usando como parámetros los elementos de un array (requiriendo el uso de clases reflexivas (eval no existe)) o saltarse la limitación de poder enviar opcionalmente referencias durante la llamada a un método (algo que en antiguas versiones de PHP sí se podía hacer) son otras restricciones que ha sido divertido sortear. Más que por complejidad, por demostrar una vez más lo estúpido que resulta poner limitaciones a un lenguaje (el caso de las referencias es sangrante) cuando puedes saltártelas igualmente si tienes un poco de imaginación…

Leer más

Ruido Perlin

Vamos con una explicación (seguida por la implementación de rigor) del algoritmo de ruido más usado en todo el mundo; el ruido Perlin.

El modelo conceptual del ruido Perlin fue descubierto en 1982 por Ken Perlin como resultado de su trabajo de generación de texturas para la película Tron. Desde entonces, el ruido Perlin, y pequeñas variantes posteriores (como el ruido Simplex) son la base para generar efectos como fuego, humo, nubes… texturas como madera, mármol, granito… mapas de altura, etc, de forma totalmente automática. En esencia, nos permite generar patrones de forma muy similar a como lo suele hacer la naturaleza.

En esta entrada vamos a explicar todo el proceso, la naturaleza del ruido, a implementarlo, y finalmente a usarlo para generar terrenos en 3D como el de la siguiente imagen (y un visor interactivo para probarlos, al final de la entrada):

noise_final

RUIDO BLANCO

Primero, entendamos las características del ruido aleatorio para comprender en qué se diferencia el ruido Perlin de él. Si generamos un mapa 2D de ruido blanco clásico (valores (pseudo)aleatorios), obtenemos algo similar a la siguiente imagen:

noise

Exacto, es muy parecido a lo que veíamos en nuestras viejas televisiones cuando no habíamos seteado ningún canal; sólo que entonces era el resultado del ruido blanco electromagnético del ambiente, completamente caótico, y en este caso sólo son bits al azar.

¿Qué características importantes vemos en esa imagen?

1) Desde el punto de vista de la teoría de la información, la entropía es máxima. Así que esta imagen no puede comprimirse al codificarse sólo con los símbolos usados en su lenguaje (blanco/negro).

2) Desde un punto de vista físico, su densidad media es prácticamente constante (tiende fuertemente a serlo) para todos los fragmentos de dimensiones superiores a 1×1. Es decir, si hacemos un desenfoque gaussiano la imagen se volverá esencialmente plana, incluso con radios de tan sólo 2px.

RUIDO PERLIN (1 dimensión)

En el ruido Perlin las características son las opuestas, que es lo deseable cuando trabajamos en generación procedural o automática. Si entendemos cómo se crea el ruido, es fácil y lógico entender el por qué de esta naturaleza.

Así que empecemos:

Vamos a calcular el ruido perlin como la generación de onda n-dimensional compuesta por k octavas. Si pensamos en la función seno para representar una onda:

sin

Vemos como su amplitud va desde -1 a 1 (2 en total) y su longitud de onda es 6. Esta bien podría ser la primera componente de nuestra onda final, en el siguiente paso, queremos una onda de la misma naturaleza, pero con mayor frecuencia (menor longitud de onda) y menor amplitud. Por ejemplo:

graph2

La tercera y cualta deberían obedecer al mismo principio (suponiendo que sólo usáramos 4):

graph3

graph4

De esta forma, si sumamos las componentes anteriores, y en general cualesquiera en las que cada una aumenta la frecuencia y reduce la amplitud de forma progresiva, obtendremos algo como lo siguiente:

graph0

Esta onda unidimensional, que se asemeja al perfil de un paisaje montañoso cumple el siguiente principio, que tanto observamos en la naturaleza: A mayor escala, menor número de variaciones pero con más influencia, a menor escala, mayor número de variaciones pero con menor influencia. O dicho de otra forma, a altas frecuencias bajas amplitudes y viceversa.

Pero en la naturaleza los procesos no están formados a base de ondas periódicas como el seno que hemos usado hasta ahora. Debemos crear una onda pseudo-aleatoria simplemente eligiendo valores aleatorios en los “nodos”. Los nodos son los puntos en los que una onda periódica alcanzaría un máximo o mínimo, es decir, debemos introducir valores cada λ unidades de espacio (longitud de onda).

Ejemplo:

puntos generados aleatoriamente para formar una onda

puntos generadores aleatoriamente para generar una onda

Una vez generados los puntos, debemos interpolar entre los valores de los nodos para rellenar todo el dominio en el que se define la onda. Podemos usar aquí cualquier tipo de interpolación: linear, trigonométrica, Lagrange, B-splines… pero usualmente con una simple interpolación linear es suficiente. Los resultados finales no mejoran demasiado con interpolaciones más complejas (como los B-Splines, que sería la ideal) y que tienen un coste computacional muy alto. No obstante sois libres de usar la que mejor os parezca.

Nota: Tengo pendiente una entrada sobre interpolación, usando B-Splines (splines cúbicos) y Lagrange, que sí son muy útiles en otros cientos de aplicaciones.

interpolación lineal

interpolación lineal

interpolación por cosenos

interpolación por cosenos

interpolación cúbica

interpolación cúbica

Para utilizar una nomenclatura adecuada, a cada componente vamos a llamarla octava, y usaremos un caso típico en el que en cada octava la frecuencia se multiplica por dos y la amplitud si divide por dos (de hecho, es así cómo se definen las octavas en música).

Con todo esto, ya podemos empezar a generar nuestra primera onda de ruido Perlin real. Empezaremos con una frecuencia de 2 (dos cambios en todo el dominio) y una amplitud de 128 (para representarla en una imagen de 256*256 siendo el centro del eje de ordenadas el centro de la imagen.

frecuencia: 2 amplitud: 128

frecuencia: 2
amplitud: 128

frecuencia: 4 amplitud: 64

frecuencia: 4
amplitud: 64

frecuencia: 8 amplitud: 32

frecuencia: 8
amplitud: 32

frecuencia: 16 amplitud: 16

frecuencia: 16
amplitud: 16

frecuencia: 32 amplitud: 6

frecuencia: 32
amplitud: 6

frecuencia: 64 amplitud: 4

frecuencia: 64
amplitud: 4

frecuencia: 128 amplitud: 2

frecuencia: 128
amplitud: 2

El resultado de sumar estas 7 octavas es el siguiente:

Ruido Perlin con 7 octavas

Ruido Perlin final con 7 octavas

Y el código que he escrito para generarlas es el siguiente:

//=============
//Interpolation
//=============
var interpolate={}
interpolate.linear=function(x1,y1,x2,y2,x)
{
	return ((x-x1)*(y2-y1)/(x2-x1))+y1;
}
//=============
//Perlin Noise
//=============
var perlin={}
perlin.octave(frequency, amplitude, length)
{
	var wave=new Array(length);
	var step_length=Math.floor(length/frequency);
	for(var i=0;i<=length;i+=step_length)
	{
		//Create the nodes of the wave
		wave[i]=(amplitude/2)-(Math.random()*amplitude);
		//Interpolation between nodes
		if(i>=step_length)
		{
			for(var j=i-(step_length-1);j<i;j++)
			{
				var x1=i-(step_length);
				var x2=i;
				wave[j]=interpolate.linear(x1,wave[x1],x2,wave[x2],j);
			}
		}
	}
	return wave;
}

Como veis el código es muy sencillo. El objeto “interpolate” está preparado para expandirse añadiendo nuevos métodos de interpolación si fuese necesario. Para generar todas las octavas y sumarlas, usando una progresión de doble frecuencia/mitad amplitud, podríamos simplemente hacer algo ad hoc como lo siguiente:

var lenght=256;
var perlin_wave=new Array(lenght);
var n_octaves=7;
for(var i=0;i<perlin_wave.length;i++) //wave initialization
	perlin_wave[i]=0;
for(var i=1;i<=n_octaves;i++)
{
	var current_pow=Math.pow(2,i);
	var octave=perlin.octave(current_pow,lenght/current_pow,lenght);
	for(var j=0;j<perlin_wave.length;j++)
		perlin_wave[j]+=octave[j];
}

O bien, podemos crear un método dentro del objeto perlin con el código anterior si lo que queremos es tenerlo encapsulado.

Con esto ya tenemos nuestro generador de ruido Perlín en una dimensión. Aunque lo interesante viene para el caso en 2D.


Prueba a generar nuevas ondas con nuestra implementación haciendo clic en el botón
Generar una nueva onda

RUIDO PERLIN (2 dimensiones)

Para el caso de 2 dimensiones debemos pensar en la onda y sus octavas como mapas o texturas. Una octava 2D con frecuencia 2 creará dos valores aleatorios por dimensión, es decir, un total de 2×2=4 valores que estarán situados en las 4 esquinas del mapa. Si la amplitud es de 256 con valores entre -128 a 128 podemos normalizarla a valores entre 0 y 256 y dibujar en escala de grises (un gris para cada valor) la onda de forma muy gráfica y representativa.

Ejemplo:

image21

En este caso, hemos rellenado el dominio vacío de la onda, con el color del punto definido que manda sobre el cuadrante; dado que como hemos dicho, sólo los vértices del mapa tienen un valor establecido. Sin embargo, lo ideal es de nuevo usar algún método de interpolación para rellenar todo el dominio. De nuevo el más usado para este caso es la interpolación lineal aplicada a dos dimensiones (bilinear), que está perfectamente explicada en la wikipedia. De esta forma, obtenemos algo como lo siguiente:

image22

Usando estos principios, podemos empezar a generar octavas y crear nuestro ruido Perlin en 2D. Por ejemplo:

frecuencia: 2 amplitud: 128

frecuencia: 2
amplitud: 128

frecuencia: 4 amplitud: 64

frecuencia: 4
amplitud: 64

frecuencia: 8 amplitud: 32

frecuencia: 8
amplitud: 32

frecuencia: 16 amplitud: 16

frecuencia: 16
amplitud: 16

frecuencia: 32 amplitud: 8

frecuencia: 32
amplitud: 8

frecuencia: 64 amplitud: 4

frecuencia: 64
amplitud: 4

frecuencia: 128 amplitud: 2

frecuencia: 128
amplitud: 2

La suma de todas estas octavas da como resultado:

Ruido Perlin 2D final con 7 octavas

Ruido Perlin 2D final con 7 octavas

Esta imagen ya tiene semejanza con algo que parecen ser nubes o humo sobre un fondo, de hecho, si dejamos el canal B a 255 y el canal G como el máximo de (185,valor_de_la_onda), habremos creado un sencillo filtro que produce imágenes como la siguiente:

nubes fluorescentes

nubes fluorescentes lanshóricas

El código para el ruido Perlin en 2D que he escrito para generar los ejemplos anteriores es el siguiente:

//=============
//Interpolation
//=============
var interpolate={}
interpolate.linear=function(x1,y1,x2,y2,x)
{
	return ((x-x1)*(y2-y1)/(x2-x1))+y1;
}
interpolate.bilinear=function(x1,y1,x2,y2,v1,v2,v3,v4,tx,ty)
{
	//P1:{x1,y1,v1} - P2:{x2,y1,v2} - P3:{x1,y2,v3} - P4:{x2,y2,v4}
	//Target:{tx,ty}
	//NOTE: Bind each area with the oposite value
	var area_v1=Math.abs((tx-x1)*(ty-y1))*v4;
	var area_v2=Math.abs((tx-x2)*(ty-y1))*v3;
	var area_v3=Math.abs((tx-x1)*(ty-y2))*v2;
	var area_v4=Math.abs((tx-x2)*(ty-y2))*v1;
	var area_total=(x2-x1)*(y2-y1);
	return (area_v1+area_v2+area_v3+area_v4)/area_total;

}
//=============
var perlin={}
perlin.octave_2d=function(frequency, amplitude, length)
{
	var step_length=Math.floor(length/frequency);
	var wave=new Array(length+1);
	for(var i=0;i<=length;i++)
		wave[i]=new Array(length);
	for(var i=0;i<=length;i+=step_length)
	{
		for(var j=0;j<=length;j+=step_length)
		{
			//Create the nodes of the wave
			wave[i][j]=(amplitude/2)-(Math.random()*amplitude);
			//Interpolation between nodes
			if(i>=step_length && j>=step_length)
			{
				for(var a=i-(step_length);a<=i;a++)
				{
					for(var b=j-(step_length);b<=j;b++)
					{
						var x1=i-(step_length);
						var x2=i;
						var y1=j-(step_length);
						var y2=j;
						wave[a][b]=interpolate.bilinear(x1,y1,x2,y2,wave[x1][y1],wave[x2][y1],wave[x1][y2],wave[x2][y2],a,b);
					}
				}
			}
		}
	}
	return wave;
}
//=============
function wave2D(data)
{
	this.map=data;
	this.add=function(data)
	{
		for(var i=0;i<data.length;i++)
		{
			for(var j=0;j<data.length;j++)
				this.map[i][j]+=data[i][j];
		}
	}
	this.render=function(ctx)
	{
		for(var i=0;i<this.map.length;i++)
		{
			for(var j=0;j<this.map.length;j++)
			{
				//Normalize from 0 to 255
				var depth=Math.floor(128+this.map[i][j]);
				//LaNsHoRic clouds: ("+depth+","+Math.max(185,depth)+",255)
				ctx.fillStyle="rgb("+depth+","+depth+","+depth+")";
				ctx.fillRect(i,j,1,1);
			}
		}
	}
	this.toURL=function()
	{
		var canvas=new Canvas();
		canvas.width=canvas.height=this.map.length;
		var ctx=canvas.getContext("2d");
		this.render(ctx);
		return canvas.toDataURL();
	}
}

Con esto, podemos adaptar el código de generación anterior a lo siguiente:

var final_wave=new wave2D(perlin.octave_2d(1,256,256));
for(var i=1;i<8;i++) //7 octaves
{
	var current_pow=Math.pow(2,i);
	var octave=perlin.octave_2d(current_pow,256/current_pow,256);
	final_wave.add(octave);
}

Prueba a generar nuevos mapas con nuestra implementación haciendo clic en el botón
Generar un nuevo mapa

Podemos cambiar la forma en que aumenta la frecuencia y disminuye la amplitud para obtener diferentes resultados. Jugando con la progresión y los valores podemos obtener texturas como granito, nubes dispersas, humo denso, etc. O usar el ruido como perturbación de un mapa base, con esto podemos generar lava, madera, mármol, relámpagos, etc. Sólo es cuestión de ir jugando con valores y experimentar un poco.

El siguiente uso popular de los mapas de ruido Perlin como el que acabamos de generar, es como mapas de altura. De esta forma podemos generar terrenos de forma automática, que en base a nuestros parámetros de generación podrán ser más o menos montañosos, más escarpados, más suaves…

En el siguiente cuadro puedes ver como usando nuestro generador de ruido Perlin creamos terrenos en 3D. Puedes hacer clic con el ratón y arrastrar para rotar la cámara. La implementación del modelado en 3D y del visor del mapa de altura están fuera del contenido de la entrada, pero prometo preparar una hablando de ello si os interesa (notificádmelo).

Como resumen rápido, dividimos un plano en tantas secciones de como frecuencia f tiene la mayor octaba. El plano queda con f*f vértices que asociamos con los píxeles del mapa 2D generado, para elevarlos según el valor del mapa en ese punto. Así el plano queda deformado como en los siguientes ejemplos:


Haz clic y arrastra el ratón para visualizar el terreno.
Generar un nuevo terreno aleatorio

Si os sirve de ayuda no es necesario mención pero sí sería cortés un agradecimiento aunque fuese en un comentario 🙂

Leer más

Recuperar código borrado

Volvemos con los problemas de la semana. Esta vez un poco de sys-admin.

A todos nos ha pasado alguna vez perder ficheros de código por accidente [hacer un rm con argumentos erróneos, usar mal el comando zip (@Erful lo sabe muy bien xD), editar y cerrar un archivo remoto (por FTP, SSH) y que un paquete se pierda por mala conexión y se destruya la copia remota y la original se sincronice vacía, etc).

Ante esto, si lleváis días o semanas trabajando en el código y no tenéis copias de seguridad, llegará el momento del pánico. Pero si mantenemos la calma y usamos todos nuestros conocimientos, hay distintas formas de recuperar el contenido perdido.

Problema: Crear un archivo con al menos unas 50 líneas de código, borrarlo, y después volver a recuperarlo (1)..

(1) Para borrarlo podemos ejecutar rm sobre él o editarlo, borrar todo el contenido y guardar (a vuestra elección).

¡Tiempo! (tic, tac, tic, tac).

Leer más

Tamaño de las tablas en BBDD

Pequeña consulta útil para controlar el tamaño de las tablas en MySQL.

SELECT table_name AS "Tables", 
round(((data_length + index_length)/1024/1024),2) "Size en MiBs" 
FROM information_schema.TABLES 
WHERE table_schema = "BD_NAME"
ORDER BY (data_length + index_length) DESC;
Leer más