Archivo

Entradas Etiquetadas ‘texto’

Generar texto e implantarlo en una imagen desde PHP

Viernes, 10 de Febrero de 2012 Gaspar Fernández 1 comentario

En ocasiones, se puede presentar la necesidad de introducir en nuestros proyectos una imagen cuyo contenido sea un texto, y sobre todo necesitamos integrarlo dentro de nuestro contenido, intentando que no se note que es una imagen, a no se que seleccionen el contenido para copiarlo.

Una de las principales utilidades de esto es evitar el SPAM al escribir nuestra dirección de e-mail en una web. Muchos clientes prefieren enviar un e-mail en lugar de utilizar un formulario de contacto y tenemos que satisfacerles, sin arriesgar la seguridad de nuestro sistema, en el sentido de que hay cientos de motores que se dedican a rastrear webs en busca de direcciones de e-mail (lo cual es muy sencillo de hacer con expresiones regulares, por ejemplo).

Tenemos que estudiar bien cómo hacemos el script, ya que la entrada de datos es esencial. Sería improductivo hacer un script cuya llamada fuera:

1
<img src="scriptGenerador.php?texto=midireccion@miservidor.com" />

Ya que un motor se enteraría de que eso es una dirección de correo igualmente. Por lo que yo sugiero algunas opciones:

  • Para simplificar, podemos codificar el texto, por ejemplo, en base 64, el e-mail anterior quedaría así:bWlkaXJlY2Npb25AbWlzZXJ2aWRvci5jb20 y sería más complicado de detectar, aunque es nuestra responsabilidad hacer la conversión. Además, si en cualquier momento queremos identificar el texto que pusimos, en cierto punto del código debemos hacer la conversión inversa. Por otra parte, aunque es difícil que algún motor se dedique a convertir el texto en base64 a binario de nuevo, es posible.
  • Otra posibilidad es, si sólo vamos a escribir un sólo texto de esta forma, implantarlo dentro del archivo php que genera la imagen, como una variable del propio código, así no debemos escribir nada cuando llamemos al script. Si tenemos que cambiar el texto, basta con editar el script
  • Si, por el contrario, vamos a escribir varios mensajes, podemos asignar una clave a cada uno, por ejemplo, un número o una cadena de texto corta, y en nuestro script hacer corresponder ese número con el texto en cuestión.

Aquí vamos a hacer un ejemplo de la tercera opción. Aunque podemos extender el ejemplo usando bases de datos (si tenemos que introducir muchos mensajes, o muchas direcciones de e-mail diferentes), aquí vamos a crear un array en PHP para hacer corresponder la clave introducida con el valor (el texto que de verdad escribimos).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?php
  // Con esta función calculamos la anchura, altura del texto y dónde está la baseline de nuestro texto
  // con el tipo de letra seleccionado.
function dimensions($ldef, $texto)
{
  // Calcular el ancho
  $box=imagettfbbox ($ldef['size'], 0, $ldef['font'], $texto);
  $width = abs(abs($box[2]) - abs($box[0]));

  // Calcular el alto y la baseline
  $box=imagettfbbox ($ldef['size'], 0, $ldef['font'], 'ILyjgq'); // Tiene caracteres que no terminan en la baseline
  $height = abs($box[7] - $box[1]);
 
  $baseline = abs($box[5]);

  return array($width, $height, $baseline);
}

// Definimos un array con los posibles textos que vamos a presentar
$textArray=array("ejemplo", "de un fragmento de texto", 'gaspy@mi_servidor.com');


// Definimos el tipo de letra y el tamaño
$letra = array ('font'  => './LinLibertine_Bd.ttf',
        'size'  => 12,
        'color' => array(0,0,0),
        );

// Definimos el texto a presentar
$textID  = (isset($_GET['texto']))?$_GET['texto']:0;
$text = $textArray[$textID];

header('Content-Type: image/png');

// Creamos la imagen, calculando primero sus dimensiones dependiendo del tamaño del texto
$box_size=dimensions($letra, $text);
$im = imagecreatetruecolor($box_size[0], $box_size[1]);

// Creamos dos colores, y definimos el color que actuará como transparente
$background = imagecolorallocate($im, 255, 255, 255);
$fontColor  = imagecolorallocate($im, $letra['color'][0], $letra['color'][1], $letra['color'][2]);
imagecolortransparent($im, $background);

// Ponemos el fondo de la imagen transparente
imagefilledrectangle($im, 0, 0, $box_size[0], $box_size[1], $background);


// Creamos el texto
imagettftext($im, $letra['size'], 0, 0, $box_size[2], $fontColor, $letra['font'], $text);

// Generamos la salida
imagepng($im);
imagedestroy($im);
?>

Para el ejemplo he utilizado la fuente Linux Libertine Bold cuyo fichero ttf es LinLibertine_Bd.ttf, en realidad podemos utilizar cualquier fuente de la que dispongamos el fichero TTF.
El script que vemos, si lo llamamos desde el navegador con el parámetro textID=1 mostrará una imagen con el texto “de un fragmento de texto” dibujado dentro con el tipo de letra especificado anteriormente.

Ahora hacemos el siguiente archivo html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Ejemplo de texto</title>
    <style text="text/css">
      <!--
     @font-face {
     font-family: "Linux Libertine";
     src: url(LinLibertine_Bd.ttf);
     }
     body{
     background: #eee;
     font: 12pt Linux Libertine,sans;
     }

     .texto{
     vertical-align:bottom;
     margin: 0;
     padding: 0;
     }
    -->
    </style>
    <meta name="author" content="Gaspar Fernández">
  </head>

  <body>
    <h1>Ejemplo de texto</h1>

    Esto es un ejemplo de inserción <img src="texto.php?textID=1" class="texto"/> incluyendo una imagen en PHP
   
    <hr>
    <address>
      <a href="http://totaki.com/poesiabinaria">Gaspar Fernández</a>,
    </address>
  </body>
</html>

Con la parte CSS:

1
2
3
4
@font-face {
     font-family: "Linux Libertine";
     src: url(LinLibertine_Bd.ttf);
     }

Estamos definiendo el fichero que se descargará para visualizar el tipo de letra, así nos aseguramos de que sea el mismo tipo de letra (el de la imagen y el de nuestro texto). El resultado debería ser algo parecido a esto:
textexample

Guardando un informe en nuestros proyectos PHP

Martes, 24 de Enero de 2012 Gaspar Fernández Sin comentarios

130120125882

Muchas veces, los proyectos en PHP crecen y crecen, y pueden ocurrir múltiples errores, tanto a la hora de crearlos como cuando ya están entregados al cliente final y debemos ofrecer soporte.

Por eso es importante ser rápido localizando los errores, y, siempre que la página, o el programa no haga algo como debe, debería tomar nota de qué ha pasado, cómo ha sido y de los datos involucrados con el fin de poder solventar el problema. Tengo que decir que debemos ser inteligentes con estos criterios, ya que un usuario malintencionado puede hacernos perder todo el espacio que tengamos disponible en nuestro servidor provocando errores; por ejemplo un script que funcione en Ajax y no se le hayan entregado los parámetros necesarios, puede ser útil saberlo en tiempo de desarrollo, pero no cuando el proyecto esté funcionando en la web, puede que incluso un motor de búsqueda mal entrenado se dedique a entrar en ese script y tumbarnos el programa.

Para hacer un informe, lo más fácil es hacerlo en un fichero de texto (podemos crear un XML sin mucho esfuerzo más, aunque va a ser algo que sólo vamos a leer nosotros y como mucho los administradores de la página, en muchos casos a los administradores no les dejaremos verlo).

Para esto, yo dispongo de una clase con funciones para guardar un informe de errores:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
<?php

  // Activa la depuración
define('__debug', true);

// Activa la visualización de los errores (sólo para modo desarrollo)
define('__debug_v', true);

class my_logger
{
  private $base_path;

  function __construct()
  {
    /** Sólo actuaremos si __debug es verdadero  */
    if (__debug)
      {
    define('__debug_file', 'my_log.log');
    define("__gf_dateformat_log", "d/m/Y H:i");

    // Si ejecutamos el programa desde consola. El directorio actual es PWD
    if (isset($_SERVER['DOCUMENT_ROOT']))
      $base_path=$_SERVER['DOCUMENT_ROOT'];
    elseif (isset($_SERVER['PWD']))
      $base_path=$_SERVER['PWD'];
    else
      $base_path='';
      }    
  }

/**
 ******************************************************************
 * @brief Escribir mensaje de error en pantalla
 *
 * To-do: Posibilidad de crear plantillas para este error desde el archivo de configuración
 *
 * @param $emsj    Mensaje de error
 *
 * @return Nada
 *
 ******************************************************************/

  private function displayerror($emsj)
  {
    return '<br /><span style="color: yellow;"'.wordwrap('DEP: '.$h->T($msj),70,"\n").'</span><br />';
  }

/**
 ******************************************************************
 * @brief Extrae el nombre de una función o un método de un array
 * de backtrace
 *
 * @param $bt_data  Array de backtrace
 * @param $index    Índice del array
 *
 * @return Array[3]={linea, Función o método (clase::metodo), fichero}
 *
 ******************************************************************/

  private function backtrace_function (&$bt_data, $index)
  {
    $bt_single=(isset($bt_data[$index]))?$bt_data[$index]:false;
    if (!$bt_single)        /* No data, no function */
      return false;

    $function=(isset($bt_single['function']))?$bt_single['function']:false;
    $class=(isset($bt_single['class']))?$bt_single['class']:false;
    $funcname=($class)?($class.'::'.$function):$function;
   
    $file=str_replace('path', '', $bt_single['file']);
    return array($bt_single['line'], $funcname, $file, $file.' ('.$funcname.', '.$bt_single['line'].')');
  }

  /**
 ******************************************************************
 * @brief Genera un fragmento de un error complejo. A menudo, esto
 *        incluye información del backtrace u otras cosas.
 *        Este método debe ser llamado desde preg_replace_callback()
 *
 * @param $data Datos enviados por preg_replace_callback()
 *
 * @return En qué se tiene que convertir la cadena enviada.
 *
 ******************************************************************/

  private function complexError($data)
  {
    if (!isset($data[1]))
      return $this->writelog('%%where%%: No se ha llamado desde preg_replace_callback(); Origen: "%%origen%%"');

    $btrace=debug_backtrace();

    switch ($data[1])
      {
      case 'where':
    $wdata=$this->backtrace_function ($btrace, 4); /* Nivel 0: complexError; Nivel 1:preg_replace_callback(); Nivel 2: writelog;  Nivel 3: wlog(); Nivel 4: El que queremos */
    return $wdata[3];
    break;
      case 'origen':
    $wdata=$this->backtrace_function ($btrace, 5); /* Nivel 3: dónde llamamos a la función */
    return $wdata[3];
    break;
      case '-origen':
    $wdata=$this->backtrace_function ($btrace, 6); /* Nivel 3: dónde llamamos a la función */
    return $wdata[3];
    break;
      case '--origen':
    $wdata=$this->backtrace_function ($btrace, 7); /* Nivel 3: dónde llamamos a la función */
    return $wdata[3];
    break;
      default:
    return $this->writelog('%%where%%: No se encuentra la palabra clave: "'.$data[1].'"');
    break;
      }
  }

/**
 ******************************************************************
 * @brief Escribimos un mensaje en el log.
 *
 * @param $error     Error a escribir
 * @param $display   Mostrar error en pantalla (sólo si __debug_v vale true)
 *
 * @return false para usarlo como return de alguna función
 *
 ******************************************************************/

  function writelog($error, $display=false)
  {
    if ((__debug) && ($error))
      {
    $errorw=preg_replace_callback('/%%(.*?)%%/', array($this, 'complexError'), $error);
    $log_f = fopen(__debug_file, "a+");
    fputs($log_f, date(__gf_dateformat_log)." - ".$errorw."\n");
    fclose($log_f);

    if ((__debug_v)&&($display))
      $this->displayerror($errorw);
      }
    return false;
  }
};

$log = new my_logger();

function wlog($error, $display=false)
{
  $GLOBALS['log']->writelog($error, $display);
}

function genera_error()
{
  wlog("Genero un error nada más empezar y digo dónde está %%where%% llamado por %%origen%%");
}

function original()
{
  genera_error();
}

original();
?>

Cada vez que encontramos un error, podemos llamar a wlog() para guardar el informe del error que se guardará en un archivo de texto.

Mientras estamos desarrollando la aplicación podemos definir __debug_v a true para visualizar algunos errores (que previamente enviaremos con wlog(”error”, true)).

Además, a la hora de enviar un error, disponemos de las palabras clave %%where%%, %%origen%%, %%-origen%% y %%–origen%% para identificar automáticamente en qué función se ha producido el error, desde qué línea se llama así como la función desde donde se llama a la que provoca el error, y la anterior…

Si utilizamos la opción para averiguar automáticamente dónde está el error (la alternativa sería escribir el nombre de la función en el texto del error) tardará un tiempo en ejecutarse, ya que estamos escrudiñando dónde se ha producido, estamos haciendo un backtrace. De hecho la función PHP que genera ese informe es debug_backtrace() y nos resultará de gran ayuda para la depuración de nuestras aplicaciones.

Dialogando con HardwareSerial y SoftwareSerial más fácil

Lunes, 29 de Agosto de 2011 Gaspar Fernández 2 comentarios

A la hora de dialogar con los Serials en Arduino, durante estos días he desarrollado funciones para leer cadenas completas de texto desde el Serial y para escribir con la sintaxis de printf(), ya que esto es mucho más fácil cuando se trata de formatear texto.

Bien, ahora se trata de unirlo todo y de dar soporte a cualquier Serial, ya sea HardwareSerial o SoftwareSerial sin complicarnos mucho la vida, con la posibilidad de cambiar esta entrada/salida en cualquier momento y así hacer nuestro programa más flexible.

Para ello he creado la biblioteca SerialExt:
SerialExt.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#ifndef SERIALEXT_H
#define SERIALEXT_H

/* Quitar comentario si no vamos a usar SoftwareSerial, así
 reducimos un poco el tamaño del ejecutable. */

/* #define SEXT_NOSOFTWARESERIAL */

#include <WProgram.h>
#include <stdio.h>
#include <stdarg.h>

/* Si no vamos a usar el Software Serial, no creamos soporte para el */
#ifndef SEXT_NOSOFTWARESERIAL
#include <dynmem.h>
#include <SoftwareSerial.h>
#endif

class SerialExt
{
public:
  ~SerialExt();
  SerialExt(const HardwareSerial &serial, long bps=19200);
  #ifndef SEXT_NOSOFTWARESERIAL
  SerialExt(uint8_t rxp=2, uint8_t txp=3, long bps=19200);
  #endif
  int readString (char *str, unsigned size, const char *stop="\0");
  void printf(const char *fmt,...);
  // A veces, podemos necesitar esto desde fuera
  void serialPrint(const char *txt);
private:
  bool serialAvailable();
  int serialRead();
  HardwareSerial *hs;
  #ifndef SEXT_NOSOFTWARESERIAL
  SoftwareSerial *ss;
  #endif
  int timeout;
};

#endif

SerialExt.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include "SerialExt.h"

void SerialExt::serialPrint(const char *txt)
{
  if (hs)
    hs->print(txt);
  #ifndef SEXT_NOSOFTWARESERIAL
  else
    ss->print(txt);
  #endif
}

void SerialExt::printf(const char *fmt,...)
{
  char tmp[128]; // resulting string limited to 128 chars
  va_list args;
  va_start (args, fmt );
  vsnprintf(tmp, 128, fmt, args);
  va_end (args);
  serialPrint(tmp);
}

SerialExt::SerialExt(const HardwareSerial &serial, long bps)
{
  hs=(HardwareSerial*)&serial;

  #ifndef SEXT_NOSOFTWARESERIAL
  ss=NULL;
  #endif
  timeout=3000000/(bps/8);  // ( 1000/(bps/8) ) * 1000 * 3.0 (milisegundos por signo por 1.5)
  // Initialize serial
  hs->begin(bps);
}

#ifndef SEXT_NOSOFTWARESERIAL
SerialExt::SerialExt(uint8_t rxp, uint8_t txp, long bps)
{
  ss=new SoftwareSerial(rxp, txp);
  hs=NULL;
  // Initialize serial
  ss->begin(bps);
}
#endif

SerialExt::~SerialExt()
{
  #ifndef SEXT_NOSOFTWARESERIAL
  if (ss)
    delete ss;
  #endif
}

// Sólo aplicable con HardwareSerial
bool SerialExt::serialAvailable()
{
  if (hs)
    return hs->available();
  else
    return true;
}

int SerialExt::serialRead()
{
  if (hs)
    hs->read();
  #ifndef SEXT_NOSOFTWARESERIAL
  else
    ss->read();
  #endif
}

int SerialExt::readString(char *str, unsigned size, const char *stop)
{
  unsigned i=0;
  char sIn;
  unsigned long m;
  // Queremos que la cadena se rellene hasta size-2 para que en el carácter
  // size-1 (el último) podamos meter el terminador \0
  --size;      
  while (serialAvailable() && i<size)
    {
      sIn=serialRead();
      if (strchr(stop, sIn))
    break;
      str[i++]=sIn;
      m=micros();
      while (!serialAvailable() && micros()<m+timeout);
    }
  str[i++]='\0';
  return i;
}

Esta biblioteca, hace uso de dynmem, para poder utilizar new y delete y crear el objeto SoftwareSerial. Aunque si no vamos a utilizar el Serial por Software, hay una directiva de pre-procesador (#define SEXT_NOSOFTWARESERIAL) que si quitamos el comentario no compilará nada del soporte, con lo que ahorraremos unos 600 bytes en el binario (que a veces, pueden salvarnos la vida).

Para probar la biblioteca, cargamos el siguiente programa de ejemplo:
serial1.h

1
2
3
4
5
6
7
/* En este archivo veremos la configuración de nuestro proyecto */

#define SERIAL_SPEED 19200
/* Led que queremos que parpadee mientras se ejecuta el programa */
#define STATUS_LED   11
/* Led que indica transferencia de datos */
#define BLINK_DELAY  500

serial1.pde:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// serial1
// Hace un eco con el puerto serie del Arduino
// Dialogamos con el Serial a través de la clase SerialExt. Desde aquí podremos dialogar
// con cualquier HardwareSerial o SoftwareSerial

#include "serial1.h"
#include <dynmem.h>
#include <SerialExt.h>
#include <SoftwareSerial.h>

#define MAX_BUFFER 100

// Almacenamos el estado como variable global
int estado=LOW;
// Almacenamos también el número de milisegundos anterior
unsigned long momento_anterior=0;
unsigned long bytes_recibidos=0;
// SerialExt SExt(Serial);

SerialExt *SExt;

void setup()
{
  // Queremos que un led parpadee mientras trabajamos
  pinMode(STATUS_LED, OUTPUT);
  digitalWrite(STATUS_LED, HIGH);
  delay(1000);
  digitalWrite(STATUS_LED, LOW);
  delay(1000);
  SExt = new SerialExt(Serial);
  digitalWrite(STATUS_LED, HIGH);
}

void loop ()
{  
  int recibe;
  unsigned long momento_actual=millis();
  char buf[MAX_BUFFER];
// No bloqueante, si hay algo para leer entramos, si no, no.
  if(Serial.available())
    {
      SExt->readString(buf, MAX_BUFFER);
      // Escribimos el buffer completo
      SExt->printf("Texto recibido: %s\n", buf);
    }
  // No usamos delay para el parpadeo porque nos entorpece la comunicación con el serial
  if (momento_actual-momento_anterior>=BLINK_DELAY)
    {
      // Cambiamos el estado siguiente. Si era HIGH (1) ahora será
      // LOW (0). He leído en algún lado que el valor de HIGH no
      // siempre es 1; pero en este caso sí es así.
      estado=!estado;
      // Escribimos el estado actual del led
      digitalWrite(STATUS_LED, estado);
      // Establecemos el momento anterior como actual.
      momento_anterior=momento_actual;
    }
}

Como vemos, SerialExt se encarga de hacer Serial.begin() y todo (tampoco es que sea gran cosa, pero nos ahorra una línea); en este ejemplo podemos ver cómo podemos intercambiar líneas completas de texto con Arduino a través del puerto serie.

Formateando la salida en el Serial para Arduino [ parte II ]

Viernes, 26 de Agosto de 2011 Gaspar Fernández Sin comentarios

El lunes pasado empecé contando formas para formatear la salida en el Serial para Arduino y me dejé dos métodos en el tintero, relacionados con codificar a mano nuestra propia función tipo printf():

printf() usando como salida el Serial

Es fácil de programar, sólo necesitamos un rato para tenerla lista, recae por completo en la biblioteca HardwareSerial, y específicamente en el objeto Serial (aunque podremos cambiarlo cuando queramos si vamos a utilizar otro puerto serie); de primeras, si necesitamos otro puerto, tendremos que cambiar todo el código, con lo que no es demasiado reutilizable. Por otra parte, nuestro binario engorda unos 2.5Kb, lo que ya es bastante sobre todo si lo vamos a usar para depuración. Pero vamos, es sólo un experimento, el printf() nativo es mejor que este:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <stdarg.h>
int contador=1;

void setup()
{
  Serial.begin(19200);
}

static void printInteger(const int num, const unsigned base, byte signd)
{
  if (signd && num<0)    
    Serial.print(num, base);
  else
    Serial.print((unsigned)num, base);
}

static void my_vprintf(const char *fmt, va_list args)
{
  // Mientras el carácter leído no sea el terminador
  while (*fmt!=0)
    {
      if (*fmt=='%')
    {
      // Ancho, Decimales y conmutador de ancho y decimales a 0
      ++fmt;        // Incrementamos la posición
      if (*fmt=='\0')
        break;      // Fin, fmt ha terminado
      else if (*fmt=='%')
        Serial.print((*fmt));   // Copiamos el % en la cadena
      else
        {
          while ( ( (*fmt>='0') && (*fmt<='9') ) || (*fmt=='.') )
        // No aceptamos formato
        ++fmt;

          unsigned b=10,s=1// ,val=va_arg(args, int)
        ;
          switch (*fmt)
        {
        case 's':
          Serial.print((char*)va_arg(args, char*));
          break;
        case 'f':
          Serial.print((double)va_arg(args, double));
          break;
        default:
          if ( (*fmt=='i') || (*fmt=='d') )
            {
            }
          else if (*fmt=='x')
            b=16;
          else if (*fmt=='X')
            b=16;
          else if (*fmt=='o')
            b=8;
          else if (*fmt=='b')
            b=2;
          else if (*fmt=='u')
            s=0;
          else
            continue;

          printInteger(va_arg(args, int), b, s);

        }
        }
    }
      else if (*fmt=='\n')
    Serial.println();
      else if (*fmt!='\0')
    Serial.print(*fmt);

      ++fmt;
    }
}

void printSerial(const char *fmt, ...)
{
  va_list args;

  // // Obtenemos la lista de argumentos
  va_start (args, fmt );
  my_vprintf(fmt, args);
  va_end (args);
  // sp.print(tmp);  
}

void loop()
{
  printSerial("Transcurridos %d o %f segundos desde que se inicio.\n", ++contador, (float)contador/10.0);

  delay(100);
}

printf() usando como salida una cadena

Es algo así como vsnprintf(), lo sacamos todo a una cadena y luego la cadena la escribimos en el Serial que queramos. Por otra parte, aunque no podemos controlar las alineaciones, sí podemos controlar la precisión y el tamaño de los valores, lo que lo hace perfecto para depuración o escritura de informes; aunque ocupa 2Kb, puede que muchas veces prefiramos vsnprintf() de toda la vida, pero esta implementación da menos problemas utilizada dentro de clases.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
#include <stdarg.h>
#define MAX_CADENA 128
#define MAX_BUFFER_BITS 33  // 32 + 1 (terminador)
#define MAX_FLOAT_SIZE 20
int contador=1;

void setup()
{
  Serial.begin(19200);
}

// void incdigit(int &var, int digit)
// {
//   var*=10;
//   var+=digit;
// }

static void printString (char *&out, const char *tstr, unsigned len, const int size, int width)
{
  while ( (len<size) && ( (*tstr!=0) || (width>0) ) )
    {
      *(out++)=(*tstr!='\0')?*(tstr++):' ';
      ++len;
      --width;
    }
}

static void printInteger(char *&out, const int num, const unsigned base, byte signd, int width, unsigned len, const int size, const char hexlet)
{
  char buffer[MAX_BUFFER_BITS];
  char *s=buffer+MAX_BUFFER_BITS-1;
  // byte neg=(signd)?(num<0):0;
  // unsigned wnum=(signd && num<0)?num*-1:num;
  unsigned wnum;
  if (signd && num<0)    
    wnum=num*-1;
  else
    wnum=num;

  *s='\0';

  if (wnum==0)
    *(s--)='0';
  else
    while (wnum)
      {
        s--;
        *s=wnum%base;
        if (*s>=10)
          *s+=hexlet-10;
        else
          *s+='0';

        wnum=wnum/base;
      }

  if (signd && num<0)
    *(s--)='-';

  printString(out, s, len, size, width);
}

static char* printFloat(char *out, const float num, unsigned decs)
{
  char *s=out+MAX_FLOAT_SIZE-1;
  float wnum=num;
  unsigned i;
  unsigned mult=1;

  *s='\0';
  if (num<0)
    wnum=num*-1;

  for (i=0; (i<decs && i<4); ++i)
    {
      wnum*=10;
      mult*=10;
    }
  unsigned long wun=(unsigned long)mult+wnum%mult;
  while (wun>1)
    {
      *(--s)=(wun%10)+'0';
      wun/=10;
    }

  *(--s)='.';

  i=(num<0)?num*-1:num;
  while (i)
    {
      *(--s)=(i%10)+'0';
      i/=10;
    }

  if (num<0)
    *(--s)='-';
 
  return s;
}

static void my_vsnprintf(char *out, int size, const char *fmt, va_list args)
{
  char *tmp=out;
  char buffer[MAX_BUFFER_BITS];
  char *pbuf;
  unsigned width,decs;
  byte widecs;
  int len;
  // Mientras el carácter leído no sea el terminador
  while (*fmt!=0)
    {
      if (out-tmp>=size)    // Miramos no pasarnos del tamaño
    break;

      if (*fmt=='%')
    {
      // Ancho, Decimales y conmutador de ancho y decimales a 0
      width=decs=widecs=0;   
      ++fmt;        // Incrementamos la posición
      if (*fmt=='\0')
        break;      // Fin, fmt ha terminado
      else if (*fmt=='%')
        *(out++)=(*fmt);    // Copiamos el % en la cadena
      else
        {
          while ( ( (*fmt>='0') && (*fmt<='9') ) || (*fmt=='.') )
        {
          if (*fmt=='.')
            widecs=1;
          else
            // incdigit((widecs)?decs:width, *fmt-'0');
            // Hacerlo a través de función suma casi 200bytes a nuestro binario y no es algo imprescindible.
            if (widecs)
              decs*=10+*fmt-'0';
            else
              width*=10+*fmt-'0';

          ++fmt;
        }

          unsigned b=10,s=1// ,val=va_arg(args, int)
        ;
          char let='a';
          switch (*fmt)
        {
        case 's':
          printString(out, va_arg(args, char*), (unsigned)(out-tmp), size, width);
          break;
        case 'f':
          printString(out, printFloat(buffer, va_arg(args, double), (widecs)?decs:-1), (unsigned)(out-tmp), size, width);
          break;
        default:
          if ( (*fmt=='i') || (*fmt=='d') )
            {
            }
          else if (*fmt=='x')
            b=16;
          else if (*fmt=='X')
            {
              b=16;
              let='A';
            }
          else if (*fmt=='o')
            b=8;
          else if (*fmt=='b')
            b=2;
          else if (*fmt=='u')
            s=0;
          else
            continue;

          printInteger(out, va_arg(args, int), b, s, width, (unsigned)(out-tmp), size, let);

        }
        }
    }
      else
    *(out++)=(*fmt);

      ++fmt;
    }
  *out='\0';
}

void printSerial(HardwareSerial &sp, const char *fmt, ...)
{
  char tmp[MAX_CADENA];
  va_list args;

  // // Obtenemos la lista de argumentos
  va_start (args, fmt );
  // // Escribimos en tmp, con tamaño MAX_CADENA, la cadena de formato será fmt y los
  // // argumentos args
  my_vsnprintf(tmp, MAX_CADENA, fmt, args);
  va_end (args);
  sp.print(tmp);  
}

void loop()
{
  printSerial(Serial, "Transcurridos %d o %f segundos desde que se inicio.\n", contador++, (float)contador/10.0);

  delay(100);
}

Recopilación de soluciones para los retos de #tuentiContest . Challenge #17

Miércoles, 29 de Junio de 2011 Gaspar Fernández Sin comentarios

Últimamente he hablado acerca del I concurso de programación de Tuenti. Un concurso de programación Online que se llevó acabo durante la semana pasada (del 13 al 20 de Junio, muy mala fecha).

Podéis ver los enunciados de todos los problemas, con ejemplos sobre la entrada y salida (aunque a veces no hay que hacerles mucho caso) en la web oficial del concurso, pero en Vidas Concurrentes lo encontramos todo en español.

Challenge #17 : The ¿? Porblem

También conocido como “Just do it.” por las palabras de su enunciado. Lo que no nos decían es que nos mandaban una imagen PNG en base64 por la entrada estándar. Si la abríamos como texto, después de muchos datos binarios podíamos leer un texto: “There is more data in this image than meets the eye“, pensemos ahora en esteganografía, por ahí iban los tiros.
Soluciones:

Si no estás en la lista y quieres plantear tu solución, deja un comentario con tu link !

Actualización 2011/07/03 01:49 : Añadida solución de @frisco82
Actualización 2011/07/03 01:59 : Añadida mi solución (@blakeyed)
Actualización 2011/07/03 13:52 : Añadida solución de @Rosapolis

Crear un archivo PHP que sólo contenga un array (desde un programa PHP)

Domingo, 9 de Enero de 2011 Gaspar Fernández Sin comentarios

Aunque puede parecer redundante, pero es una idea curiosa. Sobre todo cuando creamos un sitio web con muchas opciones. Tenemos varias opciones:

  • Guardarlas en base de datos. Con lo cual cada página que carguemos tiene que hacer una petición, y si hay muchas visitas podemos saturar el sistema. Además una petición es una tarea un poco lenta
  • Guardarlas en un archivo de texto. Por lo que tendremos que hacer un programa que lea el fichero y lo interprete. Puede llegar a ser lento si el formato de nuestro archivo es complicado.
  • Guardarlas en un fichero binario. Los problemas son parecidos a los del fichero de texto, aunque a lo mejor ahorramos algo en sintaxis al representar todo en bloques; pero si tenemos un problema podemos tardar mucho tiempo depurando y leyendo el fichero de configuración con un editor hexadecimal.
  • Archivo de texto con el array serializado (serialize()) y des-serializarlo (unserialize()) cuando vayamos a leer… Aunque no tengo claro cuánto tiempo invierten serialize() / unserialize(), pero supongo que más tiempo que una lectura simple de un array.
  • Crear un archivo PHP que contenga la configuración. Es lo más rápido ya que no tenemos que crear texto que interprete y se lee de forma nativa (hay que tener en cuenta que PHP es un lenguaje interpretado, sólo tiene que interpretar el código y no un lector que interprete la configuración como en los casos anteriores).

Vamos con este último. Estas rutinas las creé con esa idea en mente. Lo que hago es insertar un array en un archivo PHP, que luego leeré desde el programa con include()/require()/etc… y si tenemos que guardar la configuración, podemos utilizar esta función array_to_phpcode():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?php

function var2php(&$var)
{
  if (is_numeric($var))
    $o=$var;
  else
    $o='\''.addslashes($var).'\'';

  return $o;
}

function array2php(&$array)
{
  $o='';
  if (is_array($array))
    {
      $o.=' array (';
      foreach ($array as $key=>$value)
    {
      $o.=var2php($key).' => '.array2php($value).",\n";
    }
      $o.=')';
    }
  else
    $o.=var2php($array);
  return $o;
}

/**
******************************************************************
* @brief Crea código PHP con un array como contenido
*
* @param $array    Array a insertar
* @param $phpvar   Variable PHP donde vamos a asignarlo
* @param $precode  Código PHP a incluir antes
* @param $postcode Código PHP a incluir después
*
* @return Código PHP
*
******************************************************************/

function array_to_phpcode(&$array, $phpvar, $precode, $postcode, $comment=false)
{
  $o ="<?php\n/* Fichero generado automáticamente por LYB el ".date("Ymd")." a las ".date("H:i").
    " */\n".$comment."\n\n".$precode."\n\n/* Array a incluir */\n";
  $o.=$phpvar.' = '.array2php($array).';';
  $o.="\n/* Información incluida */\n".$postcode."\n?>";

  return $o;
    }

$array=array('user' => 'Gaspy',
         'blog' => 'Poesía Binaria',
         'URL'  => 'http://totaki.com/poesiabinaria/'
         );

echo array_to_phpcode($array, 'usuario', "echo \"Hola Mundo\";", "/* DESPUÉS OTRO COMENTARIO */", "/* COMENTARIO INICIAL */");
?>

Aunque a la hora de salvar la información sea poco óptimo (ya que machacamos el archivo entero con contenidos nuevos cuando tal vez sólo cambia un elemento), pero la lectura de las opciones será muy rápida (y es lo que nos interesa porque tendremos que hacerlo a cada página que se cargue). De todas formas, siempre podemos desarrollar una nueva versión que busque las diferencias y sea capaz de ubicarlas en el archivo y sustituirlas, pero seguramente nos de un código bastante más lento.

Las teclas que utilizo más frecuentemente en Emacs / Guía para principiantes

Sábado, 11 de Diciembre de 2010 Gaspar Fernández 3 comentarios

Hace unos días hablé de la importancia de conocer los atajos de teclado de tu IDE favorito; hoy hablaré de las teclas que más utilizo en el mío, Emacs, y de paso hago una pequeña guía para principiantes en este editor.

I. Un apunte básico del uso del teclado en Emacs

Lo primero que hay que mencionar es que hay muchas combinaciones con la tecla Control y Alt (o Meta), cuando una tecla, por ejemplo, la “x” se pulsa junto con Control diremos C-x y cuando esa misma tecla la pulsamos junto con Alt diremos M-x cuando pulsamos una combinación por ejemplo (M-g) y luego tenemos que soltar las teclas y pulsar una tecla sola, por ejemplo, otra g, diremos (M-g g), lo mismo vale con combinaciones, si tenemos que pulsar primero C-x y luego C-c diremos: “C-x C-c”

Si por alguna casualidad no podemos pulsar la tecla Meta, o una combinación (como sucede en algunos tipos de terminales remotos), podemos, para pulsar M-x, pulsar “Escape x”

II. Acceder al comando que queremos ejecutar

Una de las muchas cosas buenas que tiene Emacs es que todo lo que podemos hacer por teclado tiene un comando asociado, incluso para muchas acciones que no tienen tecla asociada. Por otra parte, también tenemos que decir que las teclas se pueden personalizar, por lo que estas teclas que diré (si no digo lo contrario) son las que vienen por defecto, y que casi todos los usuarios mantenemos porque suelen ser cómodas de pulsar, aunque en algunas ocasiones serían cómodas en un teclado inglés… pero te terminas acostumbrando.

Como iba diciendo, para ejecutar un comando debemos pulsar M-x y podremos, desde el minibuffer escribir el comando que queremos ejecutar, eso sí, disponemos de completion por lo que en cualquier momento podemos pulsar tab y veremos las posibilidades que tenemos. Incluso podemos pulsar M-x tab y veremos todas las posibilidades que tenemos, hay que recordar que podemos cargar extensiones y éstas pueden añadir comandos nuevos. Es más, la lista de comandos se abrirá en un nuevo buffer de Emacs, con lo que podremos buscar en ese buffer (no siempre nos acordamos de cómo empieza un comando). Para buscar también podemos hacer M-h a.

III. Me he equivocado con tanta combinación de teclas. ¿ Qué hago ?

Pulsar C-g en cualquier momento, eliminará todo lo que hemos pulsado hasta ahora. Es cierto, que a veces hay combinaciones demasiado largas, o en ocasiones hemos empezado a pulsar algo y nos arrepentimos, bien C-g es la solución para seguir por donde estábamos.

También podemos pulsar C-g si queremos cancelar una acción en curso, como por ejemplo salir, cuando tenemos muchos buffers abiertos y nos pregunta si queremos guardar o no, podemos pulsar C-g para que deje de preguntar por archivos y no salir del programa.

IV. Comandos básicos de un editor de texto

  • Abrir archivo: C-x C-f nos preguntará qué archivo abrir, podemos navegar por directorios desde el minibuffer, tenemos además la posibilidad de completar el nombre del archivo. (A lo bash)
  • Nuevo archivo: C-x C-f ¡ igual que antes ! y es que no hacemos nada diferente, abrimos un nuevo buffer. El archivo no se creará hasta que no lo salvemos por primera vez, así que, sin problema.
  • Salvar archivo: C-x C-s Salvamos el archivo con el nombre que tiene.
  • Salvar como: C-x C-w Nos pregunta con qué nombre nuevo queremos salvar el archivo
  • Salir: C-x C-c Salimos, nos preguntará
  • Buscar un texto: C-s (busca hacia adelante) C-r (busca hacia atrás) La primera vez que pulsamos cualquiera de las teclas, nos preguntará qué texto buscar, luego podemos jugar y buscar adelante y atrás como queramos.
  • Deshacer: Es posíblemente la opción peor implementada en Emacs, pero a veces hace el apaño. Podemos pulsar C-x u para deshacer un carácter o C-u C-x u para deshacer un conjunto de caracteres.
  • Seleccionar texto: Podemos hacerlo con el ratón o pulsar C-espacio, soltar y movermos con las teclas (flechas, re-pag, av-pag, inicio, fin y combinaciones.
  • Copiar: Con un texto seleccionado, pulsamos M-w
  • Cortar: Con un texto seleccionado, pulsamos C-w
  • Pegar: Pulsar C-y, pero si queremos pegar alguno de los textos copiados antes del último, justo después de C-y pulsamos M-y, e iremos navegando por todo lo que hemos ido copiando anteriormente.
  • Cambiar de ventana: C-b y seguidamente escribimos el nombre del fichero que queremos editar (de los que previamente están abiertos. O también C-x C-b para ver un listado de ficheros abiertos.
  • Cerrar buffer: C-x k Cerramos el buffer abierto ahora mismo, si correspondía con un fichero y no hay más buffers con ese fichero abierto, cerraremos el fichero y si tiene algún cambio, nos preguntará si queremos salvarlo.

V. Comandos para movernos más rápidamente

Algunos de estos son utilizados en más lugares:

  • C-k Elimina desde la posición actual del cursor hasta fin de línea. Estamos cortando el texto, por lo que la línea se copiará automáticamente.
  • M-< Saltamos al principio del buffer
  • M-> Saltamos al final del buffer
  • C-arriba / C-abajo : Nos moveremos verticalmente en bloques, es decir entre líneas en blanco o entre funciones, entre comentarios, etc
  • C-l Centra el buffer actual en la posición del cursor. Si se nos ha perdido el cursor es muy útil, o si estamos leyendo un documento.
  • M-g g nos preguntará a qué línea nos queremos ir del texto.
  • C-x h Selecciona el buffer completo
  • M-h Selecciona el párrafo actual

VI. Visualizando varios buffers a la vez

Emacs nos permite, en la misma ventana (aunque aquí se llame frame) tener varios cuadros de texto (que Emacs llama windows):

  • C-x 2 dividimos horizontalmente la pantalla, para tener un buffer arriba y otro abajo
  • C-x 3 dividimos verticalmente la pantalla, así tenemos un buffer a la derecha y otro a la izquierda.
  • C-x 0 (cero) eliminamos el buffer seleccionado
  • C-x o (letra O) cambiamos de buffer, nos vamos al de al lado, aunque también podemos seleccionarlos con ratón

VII. A la hora de programar nos será muy útil…

  • C-; Si tenemos un texto seleccionado, comentará ese texto, si no introducirá un comentario al final de la línea.
  • C-M-@ Si tenemos un texto seleccionado, se auto-indentará todo dependiendo del lenguaje de programación que estemos escribiendo.
  • M-/ Se completará automáticamente la palabra que estamos escribiendo ahora mismo.
  • Cuando hacemos C-espacio, C-s, C-r, etc vamos a irnos a otra posición diferente a la que estábamos, por lo que necesitamos una tecla para regresar: C-x C-x ; es útil cuando programamos y tenemos que escribir algo arriba del todo, y luego regresar a la línea donde seguimos escribiendo.
  • C-x r espacio Nos hará una pregunta, pulsamos una letra cualquiera, por ejemplo a. Guardará la posición en esa a
  • M-x register-to-point Nos preguntará una posición, por ejemplo, la de antes: a; y nos lanzará a esa parte del texto. (Podremos vincularlo a una tecla si lo usamos bastante.

Aunque parece que tenemos que estudiar para utilizar el editor, basta con una semana para habituarse a las teclas básicas, y poco a poco, llamando a los comandos M-x  indent-region (por ejemplo) nos dirá la tecla que debemos pulsar para acceder rápidamente.

La importancia de conocer atajos de teclado en nuestro IDE/Editor favorito

Lunes, 29 de Noviembre de 2010 Gaspar Fernández Sin comentarios

Siempre aconsejo a alguien que esté aprendiendo a programar que lo primero es sentirte bien con el entorno o IDE que manejas. A veces, en muchos centros de enseñanza se impone un entorno, por ejemplo Dev-Cpp o Borland C, pero cuando nos toca ponernos a programar algo en serio, ya sea para nosotros o para trabajar lo primero es elegir un IDE con el que nos encontremos cómodos.

Tanto para personas que están empezando como para expertos suelo hacer que prueben alguno de estos tres: Eclipse, Netbeans o Code::Blocks y que echen un rato intentando familiarizarse con el entorno, sus caprichos y su metodología.

Aunque si ya es importante familiarizarse con el entorno, ahora tenemos que hacer que valga la pena estar programando con un IDE frente a un editor de texto plano. Los IDEs tendrán muchas opciones que nos facilitarán la tarea como programadores, por ejemplo, lo primero que destacamos es que la mayoría de ellos colorean el código que escribimos; es posible que a algunas personas (conozco algún ejemplo) odien que aparezca el código en colores, incluso no lo entiendan; pero es una de las grandes herramientas que tenemos para detectar errores de un vistazo y para saber si una palabra es función/variable/texto/etc.

Pero a medida que vamos programando, tenemos la necesidad de hacer las cosas más rápido, y para ello están los atajos de teclado, por ejemplo:

  • Compilar un programa o ejecutarlo (aunque tengamos un botón para eso, tardamos mucho más tiempo en coger el ratón y hacer click que en pulsar una tecla o una combinación).
  • Copiar/cortar/pegar/salvar, es una tarea muy común, aunque no sea específica de programación, pero aún veo programadores experimentados que hacen click con el derecho o buscan el menú editar y escogen la opción que necesitan. Lo más común aquí es Control+X (cortar), Control+C (copiar), Control+V (pegar), Control +S (salvar), aunque depende del editor.
  • Auto-indentado, también llamado auto-anidado, auto-sangrado y nos permite insertar espacios o tabulaciones delante de cada línea para el correcto sangrado de cada línea de código; en ocasiones cuando copiamos o analizamos código de diversas fuentes, tal vez no esté correctamente indentado, también nos puede valer, en el proceso de aprendizaje, para ver cómo sería la forma correcta.
  • Alineado automático. A veces separamos la inicialización de un array, o los argumentos que pasamos a una función en varias líneas, pero queremos que queden todos alineados. Algunos lo hacen automáticamente, otros necesitan pulsar una tecla o elegir una opción. A veces es la misma que el auto-indentado.
  • Búsqueda de texto. Es importante localizar rápidamente un texto (variable, función, macro, etc) dentro de nuestro código y luego regresar al punto donde estábamos.
  • Ir a una línea determinada. El hecho de dirigirnos a una línea determinada es importante a la hora de ver los errores producidos y a la hora de comentar el código con otras personas
  • Comentarios automáticos. Una buena opción cuando se trata de comentar una región de texto o introducir comentarios al lado de una línea de código ya escrita.
  • Rellenado automático. Muchos IDEs permiten completar automáticamente lo que queremos escribir gracias a lo que ya hemos escrito o a los archivos / librerías que incluimos, esto nos ayuda a escribir menos y hacerlo todo más rápido.
  • División de pantalla. Algunos IDEs permiten dividir la pantalla y editar varios ficheros a la vez, o varias zonas de un mismo archivo.
  • Borrado de texto. En ocasiones nos hace falta borrar una línea entera o una palabra. Podemos seleccionar y borrar, aunque a veces lo podemos hacer con una sola pulsación de teclado.
  • Navegación de texto. Es importante familiarizarnos con las teclas Av-pag (Page Up), Re-pag (Page Down), Inicio, Fin, las flechas, y esto nos vale tanto para navegar como seleccionar texto; aunque en ocasiones, en muchos sistemas podemos utilizar Control para modifcar el comportamiento de estas, por por ejemplo, lo más común es:
    • Control + (izq/der): Navegamos entre palabras
    • Control + (arriba/abajo): Navegamos entre bloques de texto (párrafos)
    • Control + Inicio: Inicio del documento
    • Control + Fin: Fin del documento

En muchos casos no he incluido las teclas en cuestión ya que depende del IDE utilizado. Al principio puede que gastemos un tiempo investigando las teclas rápidas (muchos IDEs nos dicen la tecla rápida cuando vamos a acceder a la opción vía menú o vía comando), y tal vez al principio cuando las empezamos a usar también nos cueste trabajo, pero a la larga ahorraremos tiempo y programaremos mucho más cómodamente.

Interfaces Gráficos en Linux con gtkmm 1 [Hola Mundo]

Jueves, 14 de Octubre de 2010 Gaspar Fernández 4 comentarios

ventanaAunque a veces, cuando nos dedicamos a programar en Linux, recurrimos a la consola (ya que muchos de nosotros tenemos siempre una abierta), a veces es interesante crear un Interfaz Gráfico de Usuario (en inglés GUI, Graphical User Interface), para ello, si no queremos complicarnos demasiado tenemos dos opciones: Gtk+ y Qt.

Bien, vamos con Gtk+, está muy extendido y escrita en C, aunque aquí hablaré de una interfaz de Gtk+ para C++ llamada gtkmm, que nos proporciona las clases necesarias para jugar con la potencia de Gtk+ de una forma un poco más amigable (ya que podemos crear un botón, una etiqueta o una ventana como objetos de C++, hará todo un poco más intuitivo).

Una primera prueba que me gusta hacer es poner todo el código junto, sin mucha organización, para crear un “hola mundo”, para ver más o menos a qué nos enfrentamos y cómo, si vamos a tener que escribir mucho y esas cosas:

[ holamundo.cpp]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <gtkmm.h>

using namespace Gtk;

int main(int argc, char *argv[])
{
  // Inicialización GTK
  Main entorno(argc, argv);

  // Declaración de objetos
  Window ventana;
  Label etiqueta;

  // Características de la ventana
  ventana.set_title("Hola Mundo!");
  ventana.set_border_width(5);
  ventana.set_default_size(400, 200);

  // Etiqueta
  etiqueta.set_text("Hola Mundo!!");
  ventana.add(etiqueta);

  // Mostrar todo
  ventana.show_all_children();

  // Ejecutar GUI
  entorno.run(ventana);

  return 0;
}

Para compilar debemos hacer lo siguiente:

$ g++ -o holamundo holamundo.cpp `pkg-config –cflags gtkmm-2.4` `pkg-config –libs gtkmm-2.4`

Eso sí, debemos sustituir el 2.4 por la versión de gtkmm que tengamos instalada. Para ver cuál es, podemos hacer lo siguiente:

$ pkg-config –list-all | grep gtkmm

Bien, una vez hecho esto, vamos a intentar adquirir una metodología de programación que nos permita reaprovechar código, y tenerlo todo organizado, vamos a crear archivos .h y .cpp:

[ hworld.h ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef _HWORLD_H
#define _HWORLD_H

#include <gtkmm.h>

using namespace Gtk;

class HolaMundo : public Window
{
 public:
  HolaMundo();
  ~HolaMundo();

  Label etiqueta;
};

#endif

[ hworld.cpp ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "hworld.h"

HolaMundo::HolaMundo()
{
  this->set_title("Hola Mundo!");
  this->set_border_width(5);
  this->set_default_size(400, 200);

  etiqueta.set_text("Hola Mundo!!");
  this->add(etiqueta);

  this->show_all_children();
}

HolaMundo::~HolaMundo()
{
}

[ hellomain.cpp ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <gtkmm.h>
#include "hworld.h"

int main(int argc, char *argv[])
{
  Main entorno (argc, argv);

  // Creamos la ventana
  HolaMundo hmundo;

  // Ejecutamos
  entorno.run(hmundo);

  return 0;
}

Ahora compilamos con:

$ g++ -o hellom hellomain.cpp hworld.cpp `pkg-config –cflags gtkmm-2.4` `pkg-config –libs gtkmm-2.4`

Hará lo mismo, ¡vaya tontería! pero lo tendremos todo mucho más organizado, hemos introducido todo lo referente a la ventana en una misma clase.

Una pequeña nota: escribo arriba using namespace Gtk para no repetir todo el rato Gtk::[Tipo] , Gtk::[Método], ya que todo lo que estoy utilizando hasta ahora pertenece al espacio Gtk.

Ahora vamos a hacer algo más complicado, vamos a utilizar algunos elementos más y vamos a introducir algún botón, para que se desempeñe alguna acción:
[ hworld.h ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#ifndef _HWORLD_H
#define _HWORLD_H

#include <gtkmm.h>

using namespace Gtk;

class HolaMundo : public Window
{
 public:
  HolaMundo();
  ~HolaMundo();


  void click_salir();
  void click_mensaje();

  VBox cajaV;
  HButtonBox botonera;
  Label etiqueta;
  Button *botonSalir, *botonMensaje;
};

#endif

Creamos un objeto derivado de Gtk::Window en el que incluimos los elementos que habrá en la ventana:

  • VBox es una caja con divisiones horizontales, es como si cortáramos horizontalmente en trozos a lo largo de la vertical
  • HButtonBox es una caja para poner botones a lo largo de la horizontal
  • Button es un botón. Pondremos dos, uno para salir y otro para mostrar un mensaje emergente
  • Label es nuestra etiqueta de “Hola Mundo”

Definimos dos métodos para los eventos click_salir() y click_mensaje().

Los botones los he declarado como punteros para demostrar cómo podemos trabajar con objetos de este tipo.

[ hworld.cpp ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include "hworld.h"
#include <sstream>      // Para hacer string<<int

HolaMundo::HolaMundo()
{
  // Configuro la ventana
  set_title("Hola Mundo!");
  set_border_width(5);
  set_default_size(400, 200);

  // Configuro la etiqueta
  etiqueta.set_text("Hola Mundo!!");

  // Configuro la botonera
  botonSalir=new Button(Stock::QUIT);
  botonMensaje=new Button("Mensaje");

  botonSalir->signal_clicked().connect(sigc::mem_fun(*this, &HolaMundo::click_salir));
  botonMensaje->signal_clicked().connect(sigc::mem_fun(*this, &HolaMundo::click_mensaje));

  botonera.pack_start(*botonSalir, PACK_SHRINK);
  botonera.pack_start(*botonMensaje, PACK_SHRINK);

  // Configuro la división
  add(cajaV);
  cajaV.pack_start(etiqueta, PACK_EXPAND_WIDGET); // Esto ocupará el máximo tamaño
  cajaV.pack_start(botonera, PACK_SHRINK);

  show_all_children();
}

HolaMundo::~HolaMundo()
{
  delete botonSalir;
  delete botonMensaje;
}

void HolaMundo::click_mensaje()
{
  int res;
  std::stringstream ss;
  MessageDialog dialog("Esto es un mensaje emergente");
  res=dialog.run();

  if (res==RESPONSE_OK)
    etiqueta.set_text("Has dicho OK");
  else if (res==RESPONSE_DELETE_EVENT)
    etiqueta.set_text("Has cancelado el diálogo");
  else
    {
      ss<<"Has respondido otra cosa: "<<res;
      etiqueta.set_text(ss.str().data());
    }
}

void HolaMundo::click_salir()
{
  hide();
}

Vemos cómo en el constructor hemos configurado todos los elementos de la ventana, vemos cómo en la botonera y la cajaV, para añadir objetos se ha utilizado el método pack_start, las propiedades Gtk::PACK_SHRINK (encogerá el objeto contenedor) y Gtk::PACK_EXPAND_WIDGET (lo expanderá)

Pero la cajaV la añadiremos a la ventana con el método add.

Los botones los creamos con new (recordamos que eran punteros), tenemos muchos botones de Stock predefinidos, y podemos crear uno con esta propiedad (todos están en Gtk::Stock::XXXXX), aunque también podemos crearlos directamente con un texto.

Para conectar los eventos (de los botones por ejemplo) usamos connect(), como parámetro podemos incluir una función (con sigc::ptr_fun(&funcion)) o un método de clase (con sigc::mem_fun(*objeto, &Clase::metodo)), será la función o método que llamaremos cuando se genere el evento (en este ejemplo será el click sobre el botón).

Además, incluyo una caja de diálogo en la que luego distinguimos si se cierra por el botón aceptar (Gtk::RESPONSE_OK), o cerrando la ventana (Gtk::RESPONSE_DELETE_EVENT) modificando el texto del Label.

[ hellomain.cpp ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <gtkmm.h>
#include "hworld.h"

int main(int argc, char *argv[])
{
  Main entorno (argc, argv);

  // Creamos la ventana
  HolaMundo hmundo;

  // Ejecutamos
  entorno.run(hmundo);

  return 0;
}

Como vemos, el programa principal es exactamente igual que el anterior, como la compilación del proyecto:

g++ -o hellom hellomain.cpp hworld.cpp `pkg-config –cflags gtkmm-2.4` `pkg-config –libs gtkmm-2.4`

Jugando con ImageMagick (IV): Automatizando procesos, creando animaciones

Martes, 14 de Septiembre de 2010 Gaspar Fernández 3 comentarios

El cuarto post de la serie, anteriormente hablábamos de:

  1. Dimensiones, Captura, Color y Efectos
  2. Color (continuación) y Rotación
  3. Jugando con ImageMagick (III): Colecciones, texto, y unión de efectos

Ya que estamos hartos de ver lo que es capaz de hacer ImageMagick, ahora mezclemos esto con la potencia de la consola, y tendremos una de las herramientas más potentes jamás inventadas en cuanto a imagen digital se refiere.

Modificando imágenes en un directorio

Aunque podremos hacer cualquier tipo de modificación, tal y como hemos visto anteriormente, haremos un ejemplo en el que redimensionaremos todos los archivos de un directorio al 50% (muy útil si preparamos en un directorio muchos archivos directamente de una cámara digital para subir a Internet):

$ mogrify -resize 50% directorio/*

Con mogrify podemos hacer lo mismo que con convert, sólo que el fichero de origen y de destino es el mismo.

Aunque… ¿y si queremos guardar los archivos originales? Tendremos que hacer un pequeño script para convertir los archivos de un directorio y guardarlos en otro direcorio diferente; pero… y si son muchos ? Tenemos algunas posibilidades:

$ for f in test/*.jpg; do convert -monitor -resize 50% $f “test2/”${f##*/}; done

Si tenemos todos los archivos en el directorio test, queremos redimensionar al 50% y guardarlos en test2/ con el mismo nombre que tienen. ${f##*/} elimina el directorio del nombre del archivo, es lo mismo que la orden basename. Y con el parámetro -monitor veremos más o menos lo que está haciendo convert en cada momento.

Aunque, tal vez queramos un indicador de progreso para saber más o menos cuánto queda y cuántos archivos llevamos, lo podemos hacer con bc y Xdialog:

1
2
3
4
5
6
7
8
9
archivos=`ls test/*.jpg`;
total=`echo $archivos | wc -w`;
hecho=0;
for f in $archivos;
do
convert -resize 50% $f "test2/"${f##*/};
let hecho=$hecho+1;
echo "scale=0;$hecho*100/$total"|bc;
done | Xdialog --gauge "Progreso..." 10 100 0

En este ejemplo, separamos en variables los archivos a tratar y contamos el total de archivos a procesar. La variable hecho contará cuántos archivos ha convertido hasta el momento.

Luego en el bucle principal convertiremos los archivos e iremos incrementando la variable hecho en 1. Más adelante, con la ayuda de bc calculamos el porcentaje hecho. El bucle lo pasamos con una pipe a Xdialog, con su opción de gauge con lo que iremos indicando el proceso a medida que se vayan convirtiendo archivos.

Añadiendo un texto a todas las imágenes

Podemos, además, añadir un texto a todas las imágenes con un formato determinado para incluir el nombre de fichero, ancho, alto, la etiqueta de fichero, etc:

$ mogrify -gravity South -pointsize 20 -fill black -annotate ‘%f %b bytes” *

Con lo que anotaremos dentro de la imagen en color negro el nombre del archivo y el tamaño en bytes. Para esto tenemos algunas palabras clave como %f y %b en este enlace: Imagemagick.org (Identify -format).

hamburguesonAunque tal vez queramos poner un texto diferente a cada foto. Imaginemos que tenemos muchos ficheros jpg y muchos ficheros txt. Los ficheros txt tienen el mismo nombre que los jpg y sólo varía la extensión; y queremos plasmar el texto de los txt en los ficheros jpg:

1
2
3
4
5
6
7
8
9
10
11
for f in *.jpg;
do
if [ -r ${f%.*}.txt ];
then
txt=`cat ${f%.*}.txt`;
else
txt="Mis guisos 2010";
fi;
echo $txt en $f;
mogrify -gravity North -pointsize 15 -fill white -annotate 0x0+10+10 "$txt" $f;
done

Esta vez, si encontramos el fichero de texto con el mismo nombre que la imagen, solo que con extension txt escribiremos ese texto en la parte superior de la imagen; pero si no, escribiremos “Mis guisos 2010″ y así recorriendo todos los archivos jpg del directorio.

Una animación sencilla

destinVamos a crear una transición entre dos imágenes. Y para el ejemplo, hemos utilizado estas dos imágenes:

$ convert -size 720×576 xc:black -gravity Center -fill white -pointsize 250 -font impact -draw ‘text 0 0 A’ a.jpg

$ convert -size 720×576 xc:black -gravity Center -fill white -pointsize 250 -font impact -draw ‘text 0 0 B’ b.jpg

Para realizar el fundido hacemos lo siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
n_imgs=30; # Número de imágenes
s=`echo "scale=2;100/$n_imgs" | bc` # Porcentaje de inremento de cada paso.
o=0;   # Visibilidad de la imagen B
d=100; # Visibilidad de la imagen A
f=0;   # Fotograma actual
while (( $f &lt; 20 ));
do
convert -compose blend -set "option:compose:args" $o,$d a.jpg b.jpg -composite dest/anim_$f.jpg;
o=`echo "scale=2;$o+$s" | bc`;
d=`echo "scale=2;$d-$s" | bc`;
f=$(($f+1));
echo "Frame: $f O=$o D=$d";
done

Con esto se creará la secuencia de $n_imgs (que al principio le damos valor 30) imágenes en el directorio dest/; todas empezando por anim_.

Si queremos crear un gif animado con esto, podemos hacer lo siguiente:

$ convert dest/anim_{0..29}.jpg trans.gif

Visita otras webs de la red