C++Builder Logo
How to rotate a bitmap Comment faire tourner un bitmap d'un certain angle

1. Rotating a bitmap with an angle of  90° Faire tourner un bitmap d'un angle de 90°
We are using the Pixels property of a canvas to do the job. This is really straight forward. 
With a rotation angle of 90°, the Width of the destination bitmap becomes the Height of the source and the Height of the destination bitmap, becomes the Width of the source. Once these properties are set, we rotate each pixel one at a time.
This is easy but terribly slow for large bitmap. 
Pour effectuer une rotation de 180°, on utilise la propriété Pixels d'un canevas. Cette méthode est très simple mais très lente avec des bitmaps de taille importante.
Dans une rotation de 90°, la propriété Width du bitmap de destination est égale à la propriété Height du bitmap source et vice versa. Lorsqu'on a défini ces deux propriétés du bitmap de destination, tout ce qui reste à faire est d'effectuer la rotation un pixel à la fois.

//Open the source and create the destination bitmap 
Graphics::TBitmap *SrcBitmap=new Graphics::TBitmap; 
Graphics::TBitmap *DestBitmap=new Graphics::TBitmap; 
SrcBitmap->LoadFromFile("YourBitmap.bmp");
 
//rotate by 90° 

DestBitmap->Width=SrcBitmap->Height; 
DestBitmap->Height=SrcBitmap->Width; 

//Rotate one pixel at a time 
for (int x=0;x<SrcBitmap->Width;x++) 

  for(int y=0;y<SrcBitmap->Height;y++) 
  { 
    DestBitmap->Canvas->Pixels[y][SrcBitmap->Width-1-x]= 
       SrcBitmap->Canvas->Pixels[x][y]; 
  } 


//Assign the Destination bitmap to a TImage 
Image1->Picture->Bitmap=DestBitmap; 
delete DestBitmap; 
delete SrcBitmap;


2. Rotating a bitmap with an angle of  180° Faire tourner un bitmap d'un angle de 180°
The method is the same as the one used to rotate a bitmap with an angle of 90°. The only difference is that the width and Height of the destination bitamp are the same as the source bitmap.  La méthode est la même que pour une rotation de 90°. La seule différence est que les propriétés Width et Height du bitmap de destination sont égales à celles du bitmap source.

//Open the source and create the destination bitmap 
Graphics::TBitmap *SrcBitmap=new Graphics::TBitmap; 
Graphics::TBitmap *DestBitmap=new Graphics::TBitmap; 
SrcBitmap->LoadFromFile("YourBitmap.bmp"); 
//rotate by 180° 
DestBitmap->Width=SrcBitmap->Width; 
DestBitmap->Height=SrcBitmap->Height; 

//Rotate one pixel at a time 
for (int x=0;x<SrcBitmap->Width;x++) 

  for(int y=0;y<SrcBitmap->Height;y++) 
  { 
    DestBitmap->Canvas->Pixels[x][SrcBitmap->Height-1-y]= 
      SrcBitmap->Canvas->Pixels[x][y]; 
  } 


//Assign the Destination bitmap to a TImage 
Image1->Picture->Bitmap=DestBitmap; 
delete DestBitmap; 
delete SrcBitmap;


3. Rotating a bitmap with an angle of  any value Faire tourner un bitmap d'un angle quelconque
The method is more complex. We need some trigonometrical knowledges to do the job.
Take a look at the figure shown below :
The points 0 to 3 are the coordinates of the source bitmap and the points 0' to 3' are the coordinates of the rotated bitmap. Point 0 is the origin (0,0) of the source bitmap. The point 1 is the point (0,bitmap->Width) , the point 2 is the point (bitmap->Height,0) and the point 3 is the point (bitmap->Width,bitmap->Height). In this example, the rotation angle is equal to 45° so the coordinates become points 0' to 3' and the bounding rectangle of the rotated bitmap is quite larger than the source bitmap.
Faire tourner un bitmap d'un angle quelconque est plus compliqué. Il faudra utiliser quelques formules trigonométriques pour y arriver. Examinons la figure ci dessous :
Les points 0 à 3 représentent les coordonnées du bitmap source. Le point 0 correspond à l'origine (0,0). Le point 1 correspond au point de coordonnées (0, bitmap->Width), le point 2 au point de coordonnées (bitmap->Height,0) et le point 3 au point de coordonnées (bitmap->Width,bitamp->Height). On remarquera que la taille du rectangle dans lequel le bitmap est inscrit après rotation est plus grande que la taille du bitmap source.

rotbmp

To find the width and height of the bounding rectangle which correspond to the width and height of the destination bitmap, whe have to use the following formulas : La taille du bitmap de destination correspond a la taille du rectangle dans lequel le bitmap, après rotation, est inscrit. Pour trouver cette taille, on utilise les formules suivantes :
newx=x*cos(angle)+y*sin(angle)
newy=y*cos(angle)-x*sin(angle)
 
The math functions in C++ expect a value in radians so we have to convert degrees to radians with the formula : Les fonctions sin et cos nécessitent que l'angle soit exprimer en radians. On utilisera donc la formule suivante pour effectuer la conversion degré / radian:
radian=(2*pi*degree)/360
 
For each point 0 to 3, we calculate the coordinates of the new points 0' to 3'. The width of the bounding rectangle is equal to the maximum value of x  (for the points 0' to 3') minus the minimum value of x. The height is equal to the maximum value of y minus the minimum value of y. 

For example, if the bitmap has a width of 100 pixels, a height of 226 pixels and if we want to perform a rotation of 45° :

Pour chaque point 0 à 3, on calcule les nouveaux points 0' à 3'. La propriété Width du bitmap de destination est alors égale à la valeur maximale de x moins la valeur minimale de x. De même, la propriété Height est égale à la valeur maximale de y moins la valeur minimale de y.

Prenons un exemple. Soit un bitmap source de dimansion 100 * 226 et un angle de rotation de 45°:

point 0 (0,0) --> point 0' (0,0)
point 1 (100,0) --> point 1' (70.7,70.7)
point 2 (0,226) --> point 2' (159.7,159.7)
point 3 (100,226) --> point 3' (230.4,89)

The width of the destination bitmap (= bounding rectangle) is equal to 230 (230.4 - 0) (rounded). The height : 230 (159.7 - -70.7). La taille du bitmap de destination est alors égale à 230 * 230(230.4 - 0)et (159.7 - -70.7) après arrondi.

int angle=45;        //45° for example 
Graphics::TBitmap *SrcBitmap=new Graphics::TBitmap; 
Graphics::TBitmap *DestBitmap=new Graphics::TBitmap; 
SrcBitmap->LoadFromFile("Crayon.bmp"); 
//Convert degrees to radians 
float radians=(2*3.1416*angle)/360; 

float cosine=(float)cos(radians); 
float sine=(float)sin(radians); 

float Point1x=(-SrcBitmap->Height*sine); 
float Point1y=(SrcBitmap->Height*cosine); 
float Point2x=(SrcBitmap->Width*cosine-SrcBitmap->Height*sine); 
float Point2y=(SrcBitmap->Height*cosine+SrcBitmap->Width*sine); 
float Point3x=(SrcBitmap->Width*cosine); 
float Point3y=(SrcBitmap->Width*sine); 

float minx=min(0,min(Point1x,min(Point2x,Point3x))); 
float miny=min(0,min(Point1y,min(Point2y,Point3y))); 
float maxx=max(Point1x,max(Point2x,Point3x)); 
float maxy=max(Point1y,max(Point2y,Point3y)); 

int DestBitmapWidth=(int)ceil(fabs(maxx)-minx); 
int DestBitmapHeight=(int)ceil(fabs(maxy)-miny); 

DestBitmap->Height=DestBitmapHeight; 
DestBitmap->Width=DestBitmapWidth;


Now, the easy part, we take each pixel in the destination bitmap and get its value in the source bitmap using the same formulas. You would expect to get the value in the source bitmap and then to copy it to the destination bitmap but doing this, it leaves holes in the destination bitmap. It's because each point in the source bitmap doesn't have an equivalent in the destination bitmap. Maintenant, la partie la plus facile. On prend chaque pixel dans le bitmap de destination et on calcule à l'aide des même formules à quelle valeur correspond le pixel dans le bitmap source. Cette fçon de procéder évite les trous qui apparaissent dans le bitmap destination si on fait l'inverse : prendre chaque pixel dans la bitmap source et le copier dans le bitmap destination. Ces trous sont dû au fait que chaque pixel dans le source n'a pas nécessairement d'équivalent dans le bitmap de destination.

for(int x=0;x<DestBitmapWidth;x++) 

  for(int y=0;y<DestBitmapHeight;y++) 
  { 
    int SrcBitmapx=(int)((x+minx)*cosine+(y+miny)*sine); 
    int SrcBitmapy=(int)((y+miny)*cosine-(x+minx)*sine); 
    if(SrcBitmapx>=0&&SrcBitmapx<SrcBitmap->Width&&SrcBitmapy>=0&& 
         SrcBitmapy<SrcBitmap->Height) 
    { 
      DestBitmap->Canvas->Pixels[x][y]= 
          SrcBitmap->Canvas->Pixels[SrcBitmapx][SrcBitmapy]; 
    } 
  } 

//Show the rotated bitmap 
Image1->Picture->Bitmap=DestBitmap; 
delete DestBitmap; 
delete SrcBitmap;


Note : The min() and max() found in the code above are defines found in the windows API. If you don't want to include windows.h in your project, just redefine these macros yourself : Note : Les fonctions min() et max() utilisées dans le code ci-dessus sont en fait des macros définies dans l'API Windows. Si vous ne désirez pas inclure windows.h dans votre projet, vous pouvez redéfinir ces macros vous même :

#define min(a, b)  (((a) < (b)) ? (a) : (b)) 
#define max(a, b)  (((a) > (b)) ? (a) : (b))


4. Rotating using NT functions Rotation en utilisant les fonctions NT
There are some functions only available under Windows NT. They are really fast even with large bitmaps. The code uses the function SetWorldTransform(), the BitBlt() function and the XFORM structure. SetWorldTransform() is NOT available under windows 95/98.
You have to compute the Width and Height of the destination bitmap as shown at point 3. and then add the code below.
Sous NT, il existe une fonction SetWorldTransform(), en combinaison avec BitBlt() et la structure XFORM, qui permet d'effectuer une rotation de façon rapide même avec des bitmaps de grande taille. La fonction SetWorldTransform n'est PAS disponible sous windows 95/98.
Vous devez calculez d'abord la taille du bitmap de destination comme indiqué plus haut (point 3.) et ajouter le code ci-dessous :

SetGraphicsMode(DestBitmap->Canvas->Handle,GM_ADVANCED); 
XFORM xform; 
xform.eM11=cosine; 
xform.eM12=-sine; 
xform.eM21=sine; 
xform.eM22=cosine; 
xform.eDx=(float)-minx; 
xform.eDy=(float)-miny; 

SetWorldTransform(DestBitmap->Canvas->Handle,&xform); 

BitBlt(DestBitmap->Canvas->Handle, 
       0, 
       0, 
       DestBitmap->Width, 
       DestBitmap->Height, 
       SrcBitmap->Canvas->Handle, 
       0, 
       0, 
       SRCCOPY); 

//Show the rotated bitmap 
Image1->Picture->Bitmap=DestBitmap; 
delete DestBitmap; 
delete SrcBitmap;


5. Rotating bitmaps using DIBs Rotation en convertissant les bitmaps en DIBs
To rotate bitmap faster, you can convert your bitmap to a device independent bitmap (DIB), create a destination DIB to hold the rotated bitmap and then, convert the destination DIB to a device dependent bitmap.
Using this technique is, on my computer, seven times faster than using the Pixel property of a bitmap.
A part la méthode utilisée pour NT, les autres méthodes de rotation sont lentes. Pour améliorer la vitesse de rotation, on peut envisager de convertir le bitmap source en un device independent bitmap (DIB), créer un DIB destiné à contenir le bitmap après rotation et ensuite convertir ce DIB en bitmap que l'on peut afficher facilement.
Sur mon PC, cette technique est sept fois plus rapide que lorsqu'on utilise la propriété Pixel du bitmap.

//Source Bitmap 
Graphics::TBitmap *SrcBitmap=new Graphics::TBitmap; 
//Destination bitmap 
Graphics::TBitmap *DestBitmap=new Graphics::TBitmap; 
SrcBitmap->LoadFromFile("YourBitmap.bmp"); 
DestBitmap->PixelFormat=SrcBitmap->PixelFormat; 
int angle=45;        //45° for example 
//Convert degrees to radians 
float radians=(2*3.1416*angle)/360; 

float cosine=(float)cos(radians); 
float sine=(float)sin(radians); 

float Point1x=(-SrcBitmap->Height*sine); 
float Point1y=(SrcBitmap->Height*cosine); 
float Point2x=(SrcBitmap->Width*cosine-SrcBitmap->Height*sine); 
float Point2y=(SrcBitmap->Height*cosine+SrcBitmap->Width*sine); 
float Point3x=(SrcBitmap->Width*cosine); 
float Point3y=(SrcBitmap->Width*sine); 

float minx=min(0,min(Point1x,min(Point2x,Point3x))); 
float miny=min(0,min(Point1y,min(Point2y,Point3y))); 
float maxx=max(0,max(Point1x,max(Point2x,Point3x))); 
float maxy=max(0,max(Point1y,max(Point2y,Point3y))); 

int DestBitmapWidth=(int)ceil(maxx-minx); 
int DestBitmapHeight=(int)ceil(maxy-miny); 

DestBitmap->Height=DestBitmapHeight; 
DestBitmap->Width=DestBitmapWidth; 

//create DIB for SrcBitmap 

LPBITMAPINFO srcBmpi=(LPBITMAPINFO)new char[sizeof(BITMAPINFO)]; 
ZeroMemory(srcBmpi,sizeof(BITMAPINFO)); 
//init BitmapInfo struct 
srcBmpi->bmiHeader.biSize=sizeof(BITMAPINFOHEADER); 
srcBmpi->bmiHeader.biWidth=SrcBitmap->Width; 
srcBmpi->bmiHeader.biHeight=SrcBitmap->Height; 
srcBmpi->bmiHeader.biPlanes=1; 
srcBmpi->bmiHeader.biCompression=BI_RGB; 
switch(SrcBitmap->PixelFormat) 

  case pf24bit:   srcBmpi->bmiHeader.biBitCount=24;   break
  case pf32bit:   srcBmpi->bmiHeader.biBitCount=32;   break

//Each row is zero padded so that the number of bytes 
//per row is divisible by 4 
int SrcNumberOfBytesPerRow=
    ((((SrcBitmap->Width*srcBmpi->bmiHeader.biBitCount)+31)&~31)/8); 

//use screen DC for GetDIBits 
HDC ScreenDC=GetDC(NULL); 

//get image size 
GetDIBits(ScreenDC,SrcBitmap->Handle,0,srcBmpi->bmiHeader.biHeight, 
    NULL,srcBmpi,DIB_RGB_COLORS); 

//if the driver didn't give the size, calculate it yourselves 
if(srcBmpi->bmiHeader.biSizeImage==0) 

  srcBmpi->bmiHeader.biSizeImage= 
      SrcNumberOfBytesPerRow*SrcBitmap->Height; 


//Allocate memory for the bits 
char* srcbits=new char[srcBmpi->bmiHeader.biSizeImage]; 

//get the bits 
GetDIBits(ScreenDC,SrcBitmap->Handle,0,srcBmpi->bmiHeader.biHeight, 
    srcbits,srcBmpi,DIB_RGB_COLORS); 
  

//create DIB for DestBitmap 
LPBITMAPINFO destBmpi=(LPBITMAPINFO)new char[sizeof(BITMAPINFO)]; 
ZeroMemory(destBmpi,sizeof(BITMAPINFO)); 

//init BitmapInfo struct 
destBmpi->bmiHeader.biSize=sizeof(BITMAPINFOHEADER); 
destBmpi->bmiHeader.biWidth=DestBitmap->Width; 
destBmpi->bmiHeader.biHeight=DestBitmap->Height; 
destBmpi->bmiHeader.biPlanes=1; 
destBmpi->bmiHeader.biCompression=BI_RGB; 
destBmpi->bmiHeader.biBitCount=srcBmpi->bmiHeader.biBitCount; 
//Each row is zero padded so that the number of bytes 
//per row is divisible by 4 
int DestNumberOfBytesPerRow= 
    ((((DestBitmap->Width*destBmpi->bmiHeader.biBitCount)+31)&~31)/8); 

//get image size 
GetDIBits(ScreenDC,DestBitmap->Handle,0,destBmpi->bmiHeader.biHeight, 
    NULL,destBmpi,DIB_RGB_COLORS); 

//if the driver didn't give the size, calculate it ourselves 
if(destBmpi->bmiHeader.biSizeImage==0) 

  destBmpi->bmiHeader.biSizeImage= 
      DestNumberOfBytesPerRow*DestBitmap->Height; 


//Allocate memory for the bits 
char* destbits=new char[destBmpi->bmiHeader.biSizeImage]; 

//Set bits in destination buffer 
//Perform the rotation 
for(int y=0;y<DestBitmapHeight;y++) 

  for(int x=0;x<DestBitmapWidth;x++) 
  { 
    int SrcBitmapx=(int)((x+minx)*cosine+(y+miny)*sine); 
    int SrcBitmapy=(int)((y+miny)*cosine-(x+minx)*sine); 
    if(SrcBitmapx>=0&&SrcBitmapx<SrcBitmap->Width&&SrcBitmapy>=0&& 
        SrcBitmapy<SrcBitmap->Height) 
    { 
      for (int i=0;i<srcBmpi->bmiHeader.biBitCount/8;i++) 
      { 
        *(destbits+(DestNumberOfBytesPerRow*y)+ 
          (x*srcBmpi->bmiHeader.biBitCount/8+i))= 
            *(srcbits+(SrcNumberOfBytesPerRow*SrcBitmapy)+ 
              (SrcBitmapx*srcBmpi->bmiHeader.biBitCount/8+i)); 
      } 
    } 
  } 


//Set the bits in destination bitmap 
//Convert destination DIB to a bitmap 
SetDIBits(ScreenDC,DestBitmap->Handle,0, 
    destBmpi->bmiHeader.biHeight,destbits,destBmpi,DIB_RGB_COLORS); 
Image1->Picture->Bitmap=DestBitmap; 

delete DestBitmap; 
delete SrcBitmap; 
delete srcbits; 
delete destbits; 
delete destBmpi; 
delete srcBmpi; 


This sample is only usable with 24 or 32-bit bitmaps. Besides, I have only checked the code with 24-bit bitmaps because I don't have a 32-bit bitmap at hand. Cet exemple n'est utilisable tel quel qu'avec des bitmaps 24 ou 32-bit. De plus, je n'ai pas testé le code avec un bitmap 32-bit pour la simple raison que je n'en ai pas et que je n'ai pas de programme de dessin me permettant d'en créer un.
6. Rotating bitmaps using GDI+ Utilisation de GDI+ pour effectuer une rotation
GDI+ is the new API from Microsoft intended to replace the old GDI. GDI+ is included in XP natively and can be used in 98/ME/NT/2K if the proper DLL is installed.
It simplifies things greatly.
The first sample below, performs a 90-degrees rotation and then a vertical flip.
The second sample rotates the image by 45-degrees.

Note: Read the article GDI+ and C++ Builder to know how to install, initialize and use GDI+.
GDI+ est la nouvelle API de Microsoft destinée à remplacer GDI. Elle fait déjà partie de XP et peut être utilisée avec 98/ME/NT/2k à condition d'installer la DLL adéquate.
GDI+ simplifie énormément les choses dans le cas de rotations.
Le premier exemple, ci dessous, effectue une rotation de 90° et un renversement vertical.
Le second exemple réalise une rotation de 45°.

Note: Lisez l'article GDI+ et C++ Builder pour apprendre comment installer, initialiser et utiliser GDI+.

  Gdiplus::Graphics graphics(hdc);
  Gdiplus::Image image(L"YourPicture.gif");
  
  //First sample
  //90° rotation and vertical flip
  graphics.DrawImage(&image, 10, 10, image.GetWidth(), image.GetHeight());
  image.RotateFlip(Gdiplus::Rotate90FlipY);
  graphics.DrawImage(&image, 200, 10);



  //Second sample
  //Rotate by arbitrary angle
  //Warning: the rotation is performed from the origin (0,0).
  //All the device context (window) is rotated, not only the image.
  //First draw not-rotated image
  graphics.DrawImage(&image,0,100);
  //make a translation to make it visible at the right of the first image
  graphics.TranslateTransform(260.0f,80.0f);
  //rotate
  graphics.RotateTransform(45.0f);
  //draw rotated image
  graphics.DrawImage(&image,0,0);

Acknowledgment
Some of the code samples given in this article are based on a Microsoft knowledge base document (Q77127) from the Win32sdk and on an article written by Zafir Anjum for www.codeguru.com. Certains des exemples donnés dans cet article sont librement inspirés d'un document Microsoft (Q77127) du Win32sdk et sur un article écrit par Zafir Anjum pour www.codeguru.com.