dimanche 13 novembre 2011

É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.

Aucun commentaire: