dimanche 27 novembre 2011

Github - Ça vient doucement...

Aujourd'hui, je me suis mis à implémenter une vraie galerie d'images au lieu de la bête listbox, pour représenter la liste des images déjà importées. Ce fut donc l'occasion d'essayer de comprendre comment faire une branche sur git, et la pousser sur github. Avec l'aide de StackOverflow, voilà ce que je fis:

Tout d'abord, créer une nouvelle branche à l'aide de la commande "checkout":

# git checkout -b gallery

Git me confirme que mes changements sont maintenant bien situés sur la branche, et m'indique "Switched to a new branch 'gallery'".

# git branch

* gallery
master

C'est bien la confirmation que j'ai maintenant deux branches, et que ma branche courante est gallery. Ouf!

# git commit

# git push origin gallery

Voilà, ma branche est partie sur github. Je vérifie sur le graphe des branches que tout est bien comme je m'y attendais.

Je vais finir par y comprendre quelque chose.

mardi 22 novembre 2011

Benchmark

Je testais aujourd'hui les performances d'un nouveau bout de code pour faire de la sérialisation, templaté de partout, le comparant à du vieux code moins générique, mais réputé rapide.

Et je ne comprenais pas pourquoi mon nouveau code, sur un bout tout simple, le calcul de la taille du buffer nécessaire à la sérialisation d'un vecteur de chaînes de caractères, était plus lent. Pour un vecteur de 10000 chaînes, mon code tournait à 19µs, et l'autre à 14µs.

L'algorithme était pourtant tout bête: on prend 4 pour contenir la taille du vecteur, puis on ajoute pour chaque chaîne 4, pour la longueur de la chaine, puis la longueur de la chaîne elle-même. En pseudo-code, pour un vecteur v: 4 + (for each c in v : 4 + length(c)).

Je tente de remplacer mes itérateurs par des entiers et des index, j'enlève et remets les templates, je vérifie bien que le compilo inline tout... Toujours la différence.

Finalement, c'était tout bête. Une telle différence vient rarement des micro-optimisations du code, et bien plus souvent d'un algorithme suboptimal. Le vieux code calculait la longueur de cette manière: 4 + 4 * length(v) + (for each c in v : length(c)). Ce qui sauvait 10000 additions contre une simple multiplication, et me coûtait les 4µs.

samedi 19 novembre 2011

wxWidgets - Un OK plus standard

Avant:


Après:


La méthode wxDialog::CreateStdDialogButtonSizer créé automatiquement les boutons standards pour la plateforme, en termes de disposition (à droite, OK puis Cancel pour Windows, Cancel puis OK pour Apple et GTK, avec le texte et les icônes...). La méthode renvoie un sizer, qu'il est possible d'ajouter directement au sizer principal de la boite de dialogue.

C'est y pas plus joli comme ça?

dimanche 13 novembre 2011

OCaml détecté

Yay, Github a manifestement refait tourner les statistiques, et détecte bien maintenant que Médoc est fait à 45% d'OCaml.

Écrire son propre wxImageHandler - Lire des PDF

La bibliothèque wxWidgets fait reposer son système de chargement d'images sur la classe wxImageHandler. L'idée est d'en dériver, et de fournir l'implémentation pour lire son type d'image particulier. Une fois ajouté au framework, l'application peut lire le nouveau type d'image de manière transparente.

Voici un wxImageHandler très basique pour afficher, sous forme d'image, des fichiers PDF, en utilisant les routines de la bibliothèque ImageMagick.

Tout d'abord, le fichier d'en tête, tout simple: l'on dérive de wxImageHandler, et l'on implémente les méthodes "GetImageCount", "LoadFile", "SaveFile", et "DoCanRead".


#include <wx/wx.h>

class PdfImageHandler : public wxImageHandler
{
public:
PdfImageHandler();

virtual int GetImageCount(wxInputStream & stream);
virtual bool LoadFile(wxImage * image,
wxInputStream & stream,
bool verbose=true,
int index=-1);
virtual bool SaveFile(wxImage * image,
wxOutputStream & stream,
bool verbose=true);

protected:
bool DoCanRead(wxInputStream & stream);
};


À l'implémentation, l'on utilise l'API Magick++, et un peu de MagickCore, pour lire le fichier PDF et le renvoyer sous forme d'image.


#include "PdfImageHandler.h"

#include <Magick++.h>
#include <wx/mstream.h>

namespace
{
std::list<Magick::Image> loadImages(wxInputStream & stream)
{
wxFileOffset offset = stream.TellI();
wxMemoryOutputStream buffer;
stream.Read(buffer);
stream.SeekI(offset);

Magick::Blob blob
(buffer.GetOutputStreamBuffer()->GetBufferStart(),
buffer.GetOutputStreamBuffer()->GetBufferSize());

std::list<Magick::Image> images;
Magick::readImages(&images, blob);
return images;
}
}

PdfImageHandler::PdfImageHandler()
{
SetName(_("PdfImageHanlder"));
SetExtension(_("pdf"));
}

int PdfImageHandler::GetImageCount(wxInputStream & stream)
{
return loadImages(stream).size();
}

bool PdfImageHandler::LoadFile(wxImage * image,
wxInputStream & stream,
bool verbose,
int index)
{
std::list<Magick::Image> images = loadImages(stream);
std::list<Magick::Image>::iterator it = images.begin();
if(index != -1)
{
std::advance(it, index);
}
if(it != images.end())
{
Magick::Image & page = *it;
Magick::Blob blob;
page.write(&blob);
wxMemoryInputStream input(blob.data(), blob.length());
*image = wxImage(input);
return true;
}
else
{
return false;
}
}

bool PdfImageHandler::SaveFile(wxImage * image,
wxOutputStream & stream,
bool verbose)
{
return false;
}

bool PdfImageHandler::DoCanRead(wxInputStream & stream)
{
unsigned char hdr[4];
if(!stream.Read(hdr, WXSIZEOF(hdr)))
{
return false;
}
else
{
return memcmp(hdr, "%PDF", WXSIZEOF(hdr)) == 0;
}
}


La routine est malheureusement très inefficace: l'on se retrouvera à charger l'ensemble du PDF pour retourner le nombre de pages, puis recharger l'ensemble du PDF pour retourner chaque page. ImageMagick propose des routines permettant de "pinger" une image, c'est à dire d'en charger les propriétés sans les données. Pas sûr que le chargement de fichiers PDF, qui passe par GhostScript, permette ce genre d'astuce pour ne charger que l'image courante.

Mais si ce n'est pas rapide, au moins, ça marche! Une fois le handler ajouté, via une ligne du type
wxImage::AddHandler(new PdfImageHandler);
il devient possible de charger directement un fichier PDF dans une wxImage. L'on pourra utiliser wxImage::GetImageCount et le paramètre d'index du constructeur pour aller chercher chaque page individuellement.

lundi 7 novembre 2011

Je commence à apprécier Github

Ce site est un vrai petit bijou. Je découvre leur système de tickets, qui permet de lever des bugs ou des feature requests, de les assigner à un développeur et à une release et de les tagger. Ensuite, lorsque l'on corrige le ticket n, il suffit d'avoir "fix #n", "fixes #n", "close #n" ou "closes #n" dans le message de commit. Par exemple, mon dernier commit: "Added possibility to clear all loaded images. Fixes #1." ferme automatiquement le ticket correspondant, en y incluant le commentaire et un lien vers le commit, et ceci, pratiquement immédiatement après le push.

Par contre, étrangement, les statistiques en termes de langages utilisés ont l'air un poil cassé: j'ai commité ce week-end un millier de lignes d'OCaml et un peu de SQL, mais Github indique obstinément 100% de C++.

mercredi 2 novembre 2011

Médoc - C'est utilisable

Mon dernier commit complète la phase initiale du client lourd Médoc: il est maintenant possible d'importer un document via le scanner, puis de le sauver dans la base de données. Il aura fallu ajouter quelques boites de dialogues pour entrer la configuration de la base, et pour définir les détails du document.




Il va falloir travailler sur bon nombre de détails pour rendre le client vraiment efficace, comme par exemple pouvoir effacer ou réordonner les pages, ou sauver les paramètres de la session précédente pour ne pas avoir à tout redéfinir à chaque fois.

Le problème principal, pour l'instant, est d'arriver à trouver enfin la configuration du scanner qui fonctionne correctement. Pour mon scanner HP, SANE me sort la liste d'options suivante:



Déjà, pour "Length measurement", il faut absolument changer l'option de "Padded" à "Approximate", sinon le scanner ajoute de lui même une horrible bande blanche en dessus des documents. Ensuite, vient le souci du batch: mon scanner peut scanner une pile de documents d'un coup, dans lequel cas il faut à la fin de chaque page boucler et tester si le scanner est prêt. Si l'on a bien mis l'option "Batch", le scanner répond après la dernière page qu'il n'y a plus de documents, et tout s'arrête. Tout va bien. Si l'on oublie l'option "Batch", par contre, le scanner tourne indéfiniment.

Pire, lors du scan à plat: dans ce cas, le scanner indique toujours un document comme étant disponible. Dans ce cas, il ne faut simplement pas boucler. Il semblerait donc que je sois obligé de demander à l'utilisateur son intention. J'aurais préféré que le scan soit plus intelligent, et me retourne une condition d'arrêt dans tous les cas.

Bien entendu, ce n'est valide que pour mon modèle de scanner. À partir de là, si quiconque veut tenter l'aventure, hic sunt dracones.