Optimización 90-10

Ya dicen que el 90% del tiempo de ejecución está en el 10% del código, y en situaciones como la que menciono hoy es muy patente.

En un código para leer un determinado tipo de fichero tenía la siguiente secuencia (la función completa es unas 7 veces más larga):

for (ii = 0; ii < 3; ++ii) {
 if (tokens[ii].empty()) {
 break;
 }
 elem.clear();
 boost::split(elem, tokens[ii], boost::is_any_of("/"));

 if (elem.empty()) {
 break;
 }
 a[ii] = stoi(elem[0]) - 1;
}

Este fragmento se repite unas 80000 veces en el fichero de pruebas (los ficheros más grandes pueden requerir más de 2 millones de iteraciones). La función contenedora ya estaba optimizada en términos de uso de memoria, creación de objetos temporales, rutas más probables, etc.

Aunque en la versión de despliegue la función es suficientemente rápida (0,45 segundos de media), en modo depuración se vuelve insoportablemente lenta, en torno a los 60 segundos.

Analizando el problema se ve que lo único importante es el primer elemento del vector, es decir, lo que va desde el comienzo de tokens[ii] hasta el primer carácter “/”, por lo que la función puede re-escribirse como sigue:

for (ii = 0; ii < 3; ++ii) {
 if (tokens[ii].empty()) {
 break;
 }

 int p = tokens[ii].find('/');
 a[ii] = stoi(tokens[ii].substr(0, p)) - 1;
}

En modo depuración ahora tarda 28 segundos: ¡50% menos!. En modo final la optimización es menos notable para ficheros pequeños, ya que la función de Boost en cuestión es mucho más eficiente en este modo. De todas formas, los tiempos han mejorado y ahora tardo menos en abrir mis ficheros para corregir errores.

Daemon-start-stop

Hoy estuve creando un servicio en Debian para iniciar automáticamente un pequeño servidor (el demoniodaemon en inglés) en la empresa. El proceso en sí es bastante sencillo, basta con copiar el fichero /etc/init.d/skeleton a /etc/init.d/<nuestro_daemon>, darle permisos de ejecución y modificar los valores correspondientes a nuestro demonio.

Detallo los puntos de “ensayo y error” en los que trabajé y que me parecen más interesantes. Al final del post dejo, para los curiosos, el servicio de arranque finalizado (he cambiado el nombre por motivos internos).

  • Si se pasan argumentos al ejecutable, lo más sencillo es invocar al mismo con la ruta absoluta.
  • Los argumentos deben ser lo último en la línea de comandos del daemon-start-stop, y no justo después del –exec (como pensaría uno de primeras). Ejemplo: daemon-start-stop –start –quier –exec $DAEMON_EXEC –test — $DAEMON_ARGS
  • Muchas veces queremos ejecutar el demonio como un usuario diferente (permisos, seguridad). En ese caso, se usa el parámatro –chuid [<user>][:<group>], siendo opcionales cualquier de ellos (y siempre los argumentos de ejecución del demonio al final, recordad esto).
  • El parámetro –background ejecuta el programa en segundo plano y no tenemos que preocuparnos nosotros de hacer el fork de nuestro proceso. Importante cuando el demonio se lanza durante el arranque, para no frenarlo.
  • Si nuestro programa no crear su pidfile podemos pedirle al sistema que lo haga por nosotros con la opción –make-pidfile. El pidfile es un fichero especial del tipo /var/run/<nombreproceso>.pid que únicamente tiene dentro el identificador de proceso, y que se utiliza para localizar procesos de forma rápida.

Más información sobre el daemon-start-stop.

Continue reading

OpenSSL y la intuición: exportando claves RSA

Pocas cosas hay menos intuitivas en este mundo que el exportar las claves RSA privada y pública en OpenSSL: de entrada uno piensa que son PEM_write_bio_RSAPrivateKey y PEM_write_bio_RSAPublicKey. Todo compila, las salidas son coherentes y parece que todo va bien, pero pronto uno descubre no hay manera de hacer que el programa no falle al usar la clave pública. El problema radica en que la función a utilizar es ¡PEM_write_bio_RSA_PUBKEY!

Una explicación a fondo de la diferencia entre ambas en http://dmiyakawa.blogspot.com.es/2013/03/pemwritebiorsapubkey-vs.html.

Traducciones en Qt (y algo más)

El sistema de traducciones de Qt es muy simple: usar el método QObject::tr() para definir los textos a traducir, crear un fichero .ts para cada idioma, ejecutar el comando lupdate para actualizar esos ficheros con las nuevas frases del proyecto, traducirlas y ejecutar lrelease para generar el fichero que usa Qt con las traducciones. El sistema es muy potente, por ejemplo, no hace falta nada especial para cambiar a idiomas orientales, simplemente funciona, además de proveer ayudas a la traducción como libros de frases para tener traducciones “pre-hechas”, o detectar incoherencias en parámetros o signos de puntuación distintos entre traducciones los muestra.

Como siempre, no quiero dar un curso, sólo recopilar algunos tips nacidos del día a día.

Frases dinámicas

Igual es de los consejos más importantes y “no opcionales”. Veamos.

“El carro de Pepe”, “Pepe’s car”. Un ejemplo rápido de que cada idioma tiene construcciones diferentes. ¿Y qué? Imaginemos que queremos que nuestra aplicación pregunte el nombre al usuario y muestre el mensaje anterior. Nuestro primer código podría ser algo como:

QString msg = tr("El carro de ") + strNombre;

El problema viene al traducirlo: la cadena a traducir es únicamente “El carro de “, mientras que en nuestro código concatenaremos el nombre a continuación. No es posible traducir la frase al inglés usando este método.

Qt ofrece una solución basada en parámetros: construimos una única frase indicando el lugar de los elementos. En tiempo de ejecución sustituimos esos parámetros con el método QString::arg. Así:

QString msg = tr("El carro de %1").arg(strNombre);

La cadena a traducir en inglés tendrá la forma “%1’s car”, y funcionará perfectamente.

Cambios en los textos originales

Un proyecto es un ser vivo, y es normal que los textos mostrados cambien. Cada vez que eso ocurre hay que ejecutar lupdate para actualizar el fichero .ts. Ahora bien, al cambiar textos o quitar frases, el fichero .ts guarda las viejas entradas (muy bueno por una parte para re-traducir, especialmente si son cambios menores como un signo de puntuación o una falta de ortografía en el texto original). Conforme crece el proyecto, las viejas entradas pueden molestar más que ayudar. Para eliminarlas basta con ejecutar lupdate -noobsolete (si se usa el plugin de Visual Studio se puede especificar este parámetro en las opciones del proyecto de Qt).

Propiedades personalizadas

Es posible definir en el Qt Designer propiedades personalizadas a los QWidgets (por ejemplo, para distinguir grupos de objetos de forma sencilla). No entraré en detalle ahora de esto, prometo un post otro día. ¿Qué tiene que ver con las traducciones? Que si el valor asignado es una cadena de texto, por defecto Qt Designer lo marca como “traducible”, por lo que nuestro fichero .ts podría verse desbordado de propiedades que no queremos traducir. Para evitarlo basta con desmarcar la casilla “Traducible” de la propiedad.

One more thing…

El útlimo consejo no es directamente propio de las traducciones sino de la clase QString y del Visual C++. El editor de código usa una codificación diferente a la que usa Qt por defecto, lo que puede llevar a que algunos caracteres se muestren mal al ejecutar la aplicación. Un caso típico es el símbolo de grado (º). El problema se magnifica si ese símbolo está en una traducción, ya que entonces Qt no asociará los textos ‘origen’ y ‘traducción’ correctamente. La solución más cómoda que he encontrado es usar un parámetro para ello e insertar el carácter Unicode correspondiente:

QString degreeMsg = tr("Ahora mismo hacen 25%1").arg(QChar(0260);

Planificación

Siempre me había parecido muy aleccionador aquel chiste que dice

Sabes que has terminado tus estudios y que comienzas a trabajar cuando de 3 meses pasas a tener 3 semanas de vacaciones

Ya van siete meses desde que comencé en una empresa privada, y con lo que he visto este tiempo puedo agregar una nueva frase

Sabes que has salido de la universidad y que has entrado en la empresa privada cuando de utilizar semanas y días en tus diagramas de Gantt pasas a usar horas y minutos

Anec-notas (III)

Las vacaciones no siempre son vacaciones al 100%, cuando se trabaja en el mundo informático lo normal es pasear por todo el país con el portátil a cuestas y tener que realizar cambios sobre la marcha o pequeños ajustes a algún proyecto. Y dado que ya estaba en ésas he acá la tercera entrega de anéc-notas.

QImage

Entre los ajustes que he tenido que hacer hay uno que se refiere a la adquisición de imágenes a 60fps desde diferentes modelos de sensores. El flujo de datos involucra la visualización de dichos fotogramas en una interfaz programada en Qt. Sin entrar en más detalles, la imagen la adquiría en un QImage para su representación. Luego, con el fin de procesarla con otros algoritmos intermedios, obtenía el puntero al bloque de datos mediante un QImage::bits(). De ahí surgen los dos comentarios de esta sección.

El primero es que dicho arreglo de bytes puede contener un padding final, por lo que los píxeles de una fila no están a continuación de la anterior, sino después de un pequeño espacio. Esto no ocurre si la imagen ha sido creada desde un puntero (lo cual evita hacer una copia del bloque de memoria), pero si la imagen se manipula o se crea de otra forma hay que tener en cuenta este espacio.

El otro detalle me pareció muy curioso. Este método tiene dos variantes, una “const” y otra “no-const”. La diferencia principal es que la variante “no-const” crea una copia antes de devolver el puntero, por lo que cualquier optimización que uno crea que está haciendo al acceder directamente al bloque de memoria puede irse al pozo si no tenemos cuidado, por ejemplo, si hacemos algo como:

swap_rgb_to_gbr(img.bits());

Y la función es algo de este estilo:

void swap_rgb_to_gbr(unsigned char *pixels);

La mejor solución que he podido encontrar implica forzar un casting:

const unsigned char *buffer = img.bits();
swap_rgb_to_gbr(const_cast&lt; unsigned char* &gt;(buffer));

Ahora bien, cuando se trata de cambiar el tamaño de una imagen, QImage es poco más que un niño de 2 años con unos lápices de colores en la mano. Lo mejor es utilizar opciones más potentes, como por ejemplo OpenCV (asumiremos que src es ya una imagen de OpenCV válida):

cv::Mat dst;
cv::resize(src, dst, cv::Size(nuevo_ancho, nuevo_alto), 0.0, 0.0, cv::INTER_LANCZOS4);

QImage resizedimg(nuevo_ancho, nuevo_alto, QImage::Format_RGB888); // 24bits por píxel

const unsigned char *dstdata = dst.data;
const int rowsize = nuevo_ancho * 3; // RGB
for (int ii = 0; ii &lt; nuevo_anchoe; ++ii, dstdata += rowsize)
 memcpy(resizedimg.scanLine(ii), dstdata, rowsize);

QMenu

En lo particular me gusta tener un menú “Debug” en el cual puedo incorporar funcionalidades de depuración o que sólo deban estar presente durante el desarrollo, tales como recargar el fichero de estilos, por ejemplo. A fin de simplificar el desarrollo, este menú está siempre presente, y si la aplicación no está siendo compilada en modo depuración se quita.

Hago mención de dicho proceso acá porque no me fue evidente encontrar el cómo se hacía, así que lo dejo por escrito.

ui.menubar-&gt;removeAction(ui.menuDebug-&gt;menuAction());

Otro detalle sobre los menús es cómo establecer su color de fondo y de texto. Acá dejo un stylesheet de ejemplo.

/* Barra de menu */
QMenuBar
{
 background-image: url(:/someimage.jpeg);
}

/* Elementos de la barra de menú: título de cada menú */
QMenuBar::item
{
 background-color: transparent;
 color: #999999;
}
QMenuBar::item:selected
{
 background-color: transparent;
 color: #ffffff;
}

/* Cada menú */
QMenu
{
 background-color: #444444;
 color: #999999;
}

/* Cada acción del menú */
QMenu::item
{
 background-color: #444444;
}
QMenu::item:selected
{
 color: #ffffff;
}

Anec-notas (II)

Esta semana ha sido bastante movida ya que tenemos la entrega de un producto para la semana que viene, ya sabéis, afinar detalles y esas cosas, así que en esta segunda emisión de Anec-notas tendremos un par de puntos prácticos sobre Qt.

QLineEdit

El control QLineEdit es probablemente de los más utilizados a la hora de crear formularios. Normalmente se fija su contenido con un QLineEdit::setText(QString) al cargar el formulario, y se lee de vuelta su valor con un (QString)QLineEdit::text() al cerrar o validar el mismo. Lo que ocurre en este caso es que el flujo de la aplicación no establece puntos “finales” claros en los que validar u obtener el contenido del control (es decir, no hay un botón “Aceptar” o “Guardar”), sino que debe hacerse continuamente (cada vez que hay un cambio), guardando el contenido a la base de datos.

Con este fin es posible utilizar las señales textChanged(QString) y textEdit(QString), que se emiten cada vez que el contenido del control es modificado. La principal diferencia entre ellas radica en que textEdit sólo se emite cuando el contenido del control es modificado desde la interfaz (por el usuario), mientras que textChanged es emitida también si se cambia programáticamente mediante setText(QString). Para evitar que cada vez que inicialice el control tenga que escribir en la base de datos (operación innecesaria ya que el contenido en ese momento es el mismo), se utiliza la señal textEdit(QString).

Menús contextuales

Cuando se despliega un menú contextual, éste se apropia del mouse (grabMouse) lo que evita que los widgets que han solicitado seguir continuamente el movimiento del cursor no reciban el evento mientras el menú contextual esté desplegado. Como ejemplo práctico, imaginemos un widget que renderiza elementos gráficos propios, como un widget de OpenGL, y que mediante eventos MouseMove cambia el estado “highlighted” de los elementos que están debajo del cursor. Al desplegar el menú contextual para ese elemento, el widget deja de recibir el evento de MouseMove, por lo que cuando se oculta el menú contextual, el mouse puede estar en otra posición, resultando en una incoherencia entre el elemento resaltado y el cursor.

Los menús tienen una señal, aboutToHide(), que se emite cuando el menú se oculta, bien sea por seleccionar una entrada del menú o por desactivar el menú. Con esta señal se puede actualizar el estado de los elementos forzando un evento MouseMove. Ahora bien, este evento se emite antes de llamar al método asociado a la entrada del menú, por lo que si éste necesita del estado actual (por ejemplo, el elemento resaltado) debe tenerse este hecho en cuenta.

Algo de OpenSSL

De cara a un proyecto que he tenido que hacer en estos días, me he topado con la necesidad de utilizar OpenSSL para ciertas operaciones relacionadas con RSA, el cual no había tenido el placer de probar hasta la semana pasada.

Dejo acá un poco mi experiencia y resumen de uso, tanto en la línea de comandos como en el uso de la biblioteca de programación.

Nota: lo he usado sólo en entorno Windows, por lo que no estoy seguro si la sintaxis sea exactamente igual.

Generar el par de claves pública y privadaPrimero generamos la clave privada en formato PEM (el número al final es el tamaño de la clave, si se omite será de 512 bits):openssl genrsa -out private.pem 1024

Ahora, extraemos la clave pública:openssl rsa -pubout -in private.pem -out public.pem

Firmar ficherosPara el proyecto que mencioné necesitaba verificar la autenticidad de ciertos ficheros. Para ello decidí utilizar un protocolo de verificación bastante tradicional consistente en firmar los ficheros en cuestión con la clave privada y al recibir los ficheros desencriptarlos con la clave pública y comprobar que el contenido coincide con el del fichero sin firmar. Ambos pasos son necesarios (desencriptar y comprobar) para evitar que versiones correctamente firmadas, pero no las deseadas, puedan enviarse en lugar del fichero correcto.

Para firmar un fichero:openssl rsautl -sign -in plain.txt -out signed.txt -inkey private.pem

Podemos comprobar nosotros mismos que el fichero firmado es correcto usando:openssl rsautl -verify -in signed.txt -inkey public.pem -pubin

Se debería mostrar el contenido del fichero. En caso de que el fichero no estuviese firmado con la clave privada OpenSSL dará un error.

Verificar el fichero firmado utilizando C/C++Acá fue donde más problemas tuve y lo que me llevó a escribir este post, de forma que otros puedan aprovecharse de mis golpes al aire. Haré la menor cantidad de suposiciones posibles a fin de que esté todo claro. Sólo asumiré que las rutas de los ficheros de cabeceras y todo eso está configurado.

Ficheros de cabecera de OpenSSL necesarios:

#include <openssl/err.h>
#include <openssl/bio.h>
#include <openssl/pem.h>
#include <openssl/evp.h>
#include <openssl/rsa.h>

Al cargar la clave pública, contemplo dos opciones (aunque soy partidario de la segunda):

  1. Desde un fichero. El problema está en que fácilmente pueden cambiar la clave pública por otra y falsificar los ficheros firmados. El código sería:

    BIO *pubkeyin = BIO_new(BIO_s_file());
    if (BIO_read_filename(pubkeyin, "public.pem") <= 0)
    // Error leyendo clave pública

  2. Desde una cadena de caracteres. Esta opción es más genérica y permite, además de cargar la cadena desde un fichero, incrustarla en el ejecutable o descargarla desde un servidor, por ejemplo.

    BIO *pubkeyin = BIO_new_mem_buf(_public_key, strlen(public_key_str));

    Donde “public_key_str” es una cadena de caracteres de tipo char[].

    Nota importante si se incrusta la cadena de caracteres, y es el incluir los saltos de línea al copiar la clave al código fuente.

    char public_key_str[] = "-----BEGIN PUBLIC KEY-----\n"
    "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMe9XS8VDg6H5dgqoWeJGhwJw4\n"
    "wz4YZfj1AZnSjWJyqnVnCf4YWolwZ0xGKuR6hktsCYj759AADkRx9/2+SwFGb0pE\n"
    "6kdxeIL8V/yNSN6LJB84+LmVLjdP9or/hZ/l/XyAc9LT8q0Dl6p8mNzOoTe7e6CJ\n"
    "pz8UCfFig0TGGzmRbwIDAQAB\n"
    "-----END PUBLIC KEY-----\n";

Extraer la clave pública y preparar las estructuras de datos necesarias:

EVP_PKEY *pkey = PEM_read_bio_PUBKEY(pubkeyin, NULL, NULL, NULL);
RSA *rsa = EVP_PKEY_get1_RSA(pkey);
EVP_PKEY_free(pkey);

El contenido del fichero firmado deberá estar en una cadena de caracteres de tipo unsigned char[]:

unsigned char *rsa_in = (unsigned char *)OPENSSL_malloc(keysize * 2);
BIO *in = BIO_new_file(_targetURL.toLocal8Bit().data(), "rb");
int rsa_inlen = BIO_read(in, rsa_in, keysize * 2);

Finalmente, desencriptar el fichero firmado (RSA_PKCS1_PADDING es el valor estándar de padding al firmar ficheros):

unsigned char *rsa_out = (unsigned char *)OPENSSL_malloc(keysize + 1);
int rsa_outlen = RSA_public_decrypt(rsa_inlen, rsa_in, rsa_out, rsa, RSA_PKCS1_PADDING);
if (rsa_outlen <= 0)
// Error, fichero firmado incorrectamente
// Acá su código de error
else {
// Importante convertir la cadena leída en una cadena de caracteres válida para C
rsa_out[rsa_outlen] = 0;
}

Para comprobar la validez del fichero, habrá que comprobarlo contra el contenido esperado (en este caso almacenado en la variable “valid_str”):

bool valid = (strcmp((char *)rsa_out, valid_str) == 0);

Al finalizar, borrar las estructuras creadas. Omitiré las creadas con métodos tradicionales y dejaré las creadas mediante OpenSSL:

OPENSSL_free(rsa_out);
RSA_free(rsa);
BIO_free(pubkeyin);

Doxygen + namespaces + Qt

Desde hace algunos días me estoy dedicando a documentar unas bibliotecas para C++ que he desarrollado en mi trabajo. Para ello, como no, estoy utilizando Doxygen. Como utilizamos CMake para generar los proyectos, pues qué mejor lugar para generar la documentación. Muestro a continuación la configuración, simplemente a modo de ilustración:

#-- Add an Option to toggle the generation of the API documentationOPTION(BUILD_DOCUMENTATION "Use Doxygen to create the HTML based API documentation" ON)IF(BUILD_DOCUMENTATION)FIND_PACKAGE(Doxygen)IF(NOT DOXYGEN_FOUND)MESSAGE(FATAL_ERROR "Doxygen is needed to build the documentation. Please install it correctly.")ENDIF()#-- Configure the Template Doxyfile for our specific projectCONFIGURE_FILE(Doxyfile.in ${PROJECT_BINARY_DIR}/Doxyfile @ONLY IMMEDIATE)FILE(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/doc)#-- Add a custom target to run Doxygen when ever the project is builtADD_CUSTOM_TARGET(DocsCOMMAND ${DOXYGEN_EXECUTABLE} ${PROJECT_BINARY_DIR}/DoxyfileSOURCES ${PROJECT_BINARY_DIR}/Doxyfile)# IF you do NOT want the documentation to be generated EVERY time you build the project# then leave out the 'ALL' keyword from the above command.ENDIF()

El fichero Doxyfile.in contiene la siguiente configuración:

PROJECT_NAME = @LIBRARY_NAME@PROJECT_NUMBER = @LIBRARY_VERSION_MAJOR@.@LIBRARY_VERSION_MINOR@.@LIBRARY_VERSION_PATCH@OUTPUT_DIRECTORY = "@PROJECT_BINARY_DIR@/doc"STRIP_FROM_PATH = "@PROJECT_SOURCE_DIR@/lib"INPUT = "@PROJECT_SOURCE_DIR@/lib"RECURSIVE = YESCREATE_SUBDIRS = YESEXTRACT_PRIVATE = NOGENERATE_TODOLIST = YESGENERATE_BUGLIST = YESWARNINGS = NOWARN_IF_UNDOCUMENTED = NO

Donde @LIBRARY_NAME@ y demás son variables de entorno definidas en el fichero CMake y que hacen alusión al nombre de la biblioteca y la versión actual de la misma.

No entraré en más detalle sobre el uso de Doxygen ya que hay mucho escrito, salvo por un pequeño “problema” que tuve.

Resulta que si generamos la documentación a palo seco, los namespaces que hayamos utilizado serán incluidos en la documentación. Esto está bien hasta cierto punto: si la documentación es de una API o de una biblioteca, es muy común que todo esté dentro de un namespace común (por ejemplo, libname). En este caso toda la documentación estará plagada de libname::, libname::, libname::… y no es precisamente agradable a la vista ni fácil de leer.

Buscando en Google, rápidamente di con una posible solución (como verán, la he adaptado para incluir mi versión, pero la seguiré contando acá de todas formas):

http://stackoverflow.com/questions/6811816/doxygen-strip-top-level-namespace/11231222

El código propuesto consiste en definir una serie de macros, y reemplazar el uso del namespace principal por ellas.

#ifndef DOXY_PARSER#define LIB_NAMESPACE_STARTS namespace lib_namespace { /##/#define LIB_NAMESPACE_ENDS } /##/#define LIB_NAMESPACE lib_namespace#else#define LIB_NAMESPACE_STARTS /##/#define LIB_NAMESPACE_ENDS /##/#define LIB_NAMESPACE#endif

Con esta solución hay un problema, si usamos Qt en el proyecto (como podrán suponer era mi caso), el proceso de generación de los ficheros moc se salta por completo las macros (no las expande), y por ende el fichero .cxx generado no dispone de la información de los namespaces, lo cual, adivinaron, finaliza en un montón de errores por parte del comilador.

Una posible solución sería utilizar un #define QT_NAMESPACE lib_namespace, pero esto movería todo el espacio de nombres de Qt al de la biblioteca, y eso no es bueno.

La solución que encontré al final es más general, y funciona tanto en proyectos que utilizan Qt como los que no.

#ifndef DOXY_PARSER#define LIB_NAMESPACE lib_namespace#define LIB_NAMESPACE_STARTS namespace LIB_NAMESPACE { /##/#if (defined MOCED_FILE)#define LIB_NAMESPACE_ENDS } using namespace LIB_NAMESPACE; /##/#else#define LIB_NAMESPACE_ENDS } /##/#endif#define USING_LIB_NAMESPACE using namespace LIB_NAMESPACE; /##/#else#define LIB_NAMESPACE_STARTS /##/#define LIB_NAMESPACE_ENDS /##/#define LIB_NAMESPACE#define USING_LIB_NAMESPACE /##/#endif

Donde `MOCED_FILE` es una macro definida únicamente para los ficheros moc. Esto es sencillo si se utiliza CMake:

QT4_WRAP_CPP(mocSources qt_file1.h qt_file2.h)SET_SOURCE_FILES_PROPERTIES(${mocSources} PROPERTIES COMPILE_FLAGS "-DMOCED_FILE")

Si bien la intención original del post era comentar mis andanzas con este problema de los namespaces, decidí agregar una pequeña introducción al uso de Doxygen con CMake ya que por la red no lo encontré a la primera.

¿Necesitas un paisaje? ¡usa Wingdings!

Hoy mi compañero de despacho tenía que hacer unos experimentos de tracking para un artículo; no me acuerdo bien qué tenía que hacer pero involucraba algún escenario sintético tipo paisaje. ¿De dónde cuernos saca uno un paisaje sintético del que tengamos el modelo CAD y que podamos reproducirlo en la vida real para filmarlo luego, sin tener que hacer el pino?

Pues a Hugo se le ocurrió una idea simple pero eficiente: se hizo un documento donde escribió tres letras usando la fuente Wingdings, esa que nadie usa pero que está llena de figuritas. Pues hizo un maravilloso paisaje sintético, lo pasó a CAD usando detección de bordes, e imprimió el documento para poderlo filmar luego. Hizo honor a su título de ingeniero, es decir, aquél que usa el ingenio ;)

Les dejo una imagen con el documento y el sistema de tracking funcionando para que disfruten un rato.

Para más información, y haciendo publicidad gratuita, pueden echarle un vistazo a su tesis doctoral.