Le format bitmap (.bmp)
On va utiliser le format Bitmap pour écrire et lire des images, entre autre pour des raisons de portabilité (il est lisible sur la plupart des plateformes) et de simplicité. Un fichier contenant une image Bitmap est composé d’une en-tête, suivie d’une éventuelle palette de couleurs et des données des pixels. Pour simplifier l’écriture de la classe Bitmap, on va se limiter à un seul format non compressé et qui n’utilise pas de palette: RGB24.

Dans ces conditions, un fichier Bitmap possède la structure binaire suivante:

  • deux caractères successifs qui doivent être ’B’ et ’M’;
  • une en-tête qui est une structure BITMAPINFOHEADER contenant diverses informations (taille, résolution, etc..); les différents membres de la structure doivent être placés côte-à-côte, ce que le C ANSI ne permet pas de spécifier. Toutefois, la plupart des compilateurs proposent la directive #pragma pack(...) qui permet de contrôler les espaces éventuels que le compilateur pourrait insérer entre les membres pour optimiser leur accès;
  • les trois valeurs des canaux des pixels successifs de chaque ligne, de la plus haute à la plus basse, de la gauche vers la droite, dans l’ordre B, G puis R et échantillonnés sur un octet (unsigned char); toutefois, pour des raisons historiques, les spécifications du format imposent en plus que chaque début de ligne doit être aligné sur un multiple de 32 bits (4 octets) par rapport au début du fichier. Par exemple, si la largeur de l’image est de six pixels, une ligne occupe 6×3=18 octets, on devra donc insérer deux octets supplémentaires (18+2=20=4×5) avant d’écrire la ligne suivante.

Voici la définition du type BITMAPINFOHEADER, tel que tiré de la documentation de Microsoft:

typedef struct tagBITMAPINFOHEADER{ DWORD biSize; /* Taille de l'en-tête (doit être égale à sizeof BITMAPINFOHEADER) */ LONG biWidth; /* Largeur de l'image en pixels */ LONG biHeight; /* Hauteur */ WORD biPlanes; /* Nombre de plans (toujours égal à 1) */ WORD biBitCount; /* Nombre de bits par pixel (égal à 24 pour le format RGB24) */ DWORD biCompression; /* Compression (égal à 0 si non compressé) */ DWORD biSizeImage; /* Taille totale de l'image (octets d'alignement compris) */ LONG biXPelsPerMeter; /* Facteur d'échelle pour la taille d'impression */ LONG biYPelsPerMeter; DWORD biClrUsed; /* Taille de la palette */ DWORD biClrImportant; /* Couleurs importantes de la palette */ } BITMAPINFOHEADER, *PBITMAPINFOHEADER;

Le format RGB24 défini à partir de caractères non signés est pratique pour un stockage compact des données, mais pas pour leur manipulation. Par exemple certaines opérations de filtrage font intervenir des coefficients flottants ou négatifs, qui risquent d’introduire des erreurs de calcul dues à un sous-échantillonnage numérique ou de dépassement de capacité entière si on stockait les données seulement sur huit bits. L’intérêt de définir une classe template pour manipuler les images Bitmap est qu’il est possible de paramétrer le type de représentation des données en fonction de l’usage qu’on veut en faire.

Le fichier suivant expose une nouvelle classe: Bitmap. Elle permet de lire/écrire une image dans un fichier, et de manipuler les données des pixels individuellement.

#ifndef __bitmap_h__ #define __bitmap_h__ #include <iostream> #include <fstream> #include <stdlib.h> #include <assert.h> #ifdef LINUX # define SHOW(_S) system("kv " _S " || xv " _S); #else # define SHOW(_S) system(_S); #endif #define SAVE_AND_SHOW(_I,_S) _I.saveFile(_S);SHOW(_S); /********************************************************************* * RGB & OPERATORS *********************************************************************/ template <typename T=unsigned char> struct RGB { T R,G,B; template<typename U> RGB<T> operator = (RGB<U> X){ R=X.R; G=X.G; B=X.B; return *this; }; RGB<T> operator + (){ return *this; }; RGB<T> operator - (){ R=-R; G=-G; B=-B; return *this; }; template<typename U> RGB<T> operator + (RGB<U> X){ RGB<T> Y={R+X.R,G+X.G,B+X.B}; return Y; }; template<typename U> RGB<T> operator - (RGB<U> X){ RGB<T> Y={R-X.R,G-X.G,B-X.B}; return Y; }; template<typename U> RGB<T> operator * (U K){ RGB<T> Y={R*K,G*K,B*K}; return Y; }; template<typename U> RGB<T> operator * (RGB<U> X){ RGB<T> Y={R*X.R,G*X.G,B*X.B}; return Y; }; template<typename U> RGB<T> operator / (U K){ RGB<T> Y={R/K,G/K,B/K}; return Y; }; template<typename U> RGB<T> operator / (RGB<U> X){ RGB<T> Y={R/X.R,G/X.G,B/X.B}; return Y; }; template<typename U> RGB<T> operator += (RGB<U> X){ R+=X.R; G+=X.G; B+=X.B; return *this; }; template<typename U> RGB<T> operator -= (RGB<U> X){ R-=X.R; G-=X.G; B-=X.B; return *this; }; template<typename U> RGB<T> operator *= (U K){ R*=K; G*=K; B*=K; return *this; }; template<typename U> RGB<T> operator *= (RGB<U> X){ R*=X.R; G*=X.G; B*=X.B; return *this; }; template<typename U> RGB<T> operator /= (U K){ R/=K; G/=K; B/=K; return *this; }; template<typename U> RGB<T> operator /= (RGB<U> X){ R/=X.R; G/=X.G; B/=X.B; return *this; }; }; template<typename T,typename U> RGB<U> operator * (T K,RGB<U> X){ RGB<U> Y={X.R*K,X.G*K,X.B*K}; return Y; }; template<typename T> RGB<T> rgb(T r,T g,T b){ RGB<T> x={r,g,b}; return x; }; template<typename T> RGB<T> gray(T value){ RGB<T> x={value,value,value}; return x; }; RGB<int> black() {return rgb(0x00,0x00,0x00);}; RGB<int> white() {return rgb(0xFF,0xFF,0xFF);}; RGB<int> red() {return rgb(0xFF,0x00,0x00);}; RGB<int> green() {return rgb(0x00,0xFF,0x00);}; RGB<int> blue() {return rgb(0x00,0x00,0xFF);}; RGB<int> yellow() {return rgb(0xFF,0xFF,0x00);}; RGB<int> cyan() {return rgb(0x00,0xFF,0xFF);}; RGB<int> violet() {return rgb(0xFF,0x00,0xFF);}; /********************************************************************* * CLASS BITMAP *********************************************************************/ #pragma pack(push,1) struct bitmapheader{ unsigned long int Size; long int Width; long int Height; unsigned short int Planes; unsigned short int BitCount; unsigned long int Compression; unsigned long int SizeImage; long int XPelsPerMeter; long int YPelsPerMeter; unsigned long int ClrUsed; unsigned long int ClrImportant; }; struct bitmapfileheader{ char Tag[2]; unsigned long int Size; unsigned long int Reserved; unsigned long int DataOffset; struct bitmapheader Header; }; #pragma pack(pop) template <typename T=unsigned char> class Bitmap { public: typedef struct RGB<T> RGB; private: int ModMirror(int x,const int l) const { if (x<0) x=-x; x=x % (2*l-1); if (x>=l) x=2*l-2-x; assert(x>=0 && x<l); return x; }; int align32(const int x) const { int y=x % 4; return y?x+(4-y):x; }; protected: int Width,Height; RGB *Pixels; public: Bitmap(){ Pixels=NULL; }; Bitmap(char *filename){ Pixels=NULL; loadFile(filename); }; Bitmap(const int size){ Pixels=NULL; setSize(size,size); }; Bitmap(const int width,const int height){ Pixels=NULL; setSize(width,height); }; ~Bitmap(){ if (Pixels!=NULL) free(Pixels); }; RGB* getData(){ return Pixels; } RGB &operator()(int x,int y){ return Pixels[ModMirror(x,Width)+ModMirror(y,Height)*Width]; }; RGB rgb(T r,T g,T b) const{ RGB x={r,g,b}; return x; }; RGB gray(T value) const{ RGB x={value,value,value}; return x; }; RGB black() const{return rgb(0x00,0x00,0x00);}; RGB white() const{return rgb(0xFF,0xFF,0xFF);}; RGB red() const{return rgb(0xFF,0x00,0x00);}; RGB green() const{return rgb(0x00,0xFF,0x00);}; RGB blue() const{return rgb(0x00,0x00,0xFF);}; RGB yellow() const{return rgb(0xFF,0xFF,0x00);}; RGB cyan() const{return rgb(0x00,0xFF,0xFF);}; RGB violet() const{return rgb(0xFF,0x00,0xFF);}; int getWidth(void) const{ return Width; }; int getHeight(void) const{ return Height; }; void setSize(const int width,const int height){ assert(width>=0 && height>=0); Width=width; Height=height; if (width*height && !(Pixels=(RGB *) realloc(Pixels,Width*Height*sizeof(RGB)))) throw "Memory allocation failed\n"; }; void setSize(const int size){ setSize(size,size); }; void setWidth(const int width){ setSize(width,Height); }; void setHeight(const int height){ setSize(Width,height); }; void fill(const RGB x){ for (int i=Width*Height-1;i>=0;i--) Pixels[i]=x; } void loadStream(std::istream &s){ bitmapfileheader h; int c; char *t,*u; s.read((char *) &h,sizeof(bitmapfileheader)); #define ASSERT(_c,_s) if (!(_c)) throw (_s); ASSERT(h.Tag[0]=='B',"Invalid bitmap image signature in bitmap image header"); ASSERT(h.Tag[1]=='M',"Invalid bitmap image signature in bitmap image header"); ASSERT(h.Header.Size==sizeof(bitmapheader),"Wrong header length in bitmap image header"); ASSERT(h.Header.Planes==1,"Invalid planes count in bitmap image header"); ASSERT(h.Header.BitCount==24,"Only 24 BBP Bitmaps are supported"); ASSERT(h.Header.Compression==0,"Wrong pixel format in bitmap image header"); ASSERT(h.Header.Width*h.Header.Height>0,"Invalid image size"); #undef ASSERT setSize(h.Header.Width,h.Header.Height); c=align32(Width*3); t=new char[c]; try { for (int a=0;a<Height;a++){ s.read(t,c); u=t; for (int b=0;b<Width;b++){ RGB &p=Pixels[a*Width+b]; p.B=*u++; p.G=*u++; p.R=*u++; } } } catch (...){ delete[] t; throw; } delete[] t; }; void saveStream(std::ostream &s) const{ bitmapfileheader h={{'B','M'},0,0,sizeof(bitmapfileheader),{sizeof(struct bitmapheader),Width,Height,1,24,0,0,0,0,0,0}}; int c=align32(Width*3); char *t,*u; s.write((char *) &h,sizeof(bitmapfileheader)); t=new char[c]; try { for (int a=0;a<c;a++) t[a]=0; for (int a=0;a<Height;a++){ u=t; for (int b=0;b<Width;b++){ RGB &p=Pixels[a*Width+b]; *u++=(unsigned char) p.B; *u++=(unsigned char) p.G; *u++=(unsigned char) p.R; } s.write(t,c); } } catch (...){ delete[] t; throw; } delete[] t; }; void loadFile(const char *filename){ std::ifstream s(filename,std::fstream::binary); loadStream(s); }; void saveFile(const char *filename){ std::ofstream s(filename,std::fstream::binary); saveStream(s); }; }; template<typename T> std::istream &operator << (std::istream &s,Bitmap<T> &b){ b.saveStream(s); return s; }; template<typename T> std::ostream &operator >> (std::ostream &s,Bitmap<T> &b){ b.loadStream(s); return s; }; #endif
Dernière modification le 18/3/2010
Ce document a été traduit de LaTeX par HeVeA