Programmieren mit C++

Borland C++

Grafik

Farb-Bitmaps auf Grauwertbilder ein- und ausblenden

Wie kann eine Farbbitmap in eine Grauwertbitmap übergeblendet werden und umgekehrt eine Farbbitmap ausgehend von einer Grauwertdarstellung auf die volle Farbbitmap?

Frage

Die nachfolgend vorgestellte Funktion FadeGrayScaleToColor()zeichnet eine geräteunabhängige Bitmap (DIB) auf einen Ausgabekontext als Grauwertbild und blendet dann nach und nach die Farben ein, bis die volle Farbbitmap angezeigt wird. Allerdings ist die Funktion in der vorliegenden Variante auf DIBs mit bis zu 256 Farben beschränkt.

Lösung A

Für die Abbildung der Farben auf eine Palette wird die früher vorgestellte Funktion CreateReservedPalette() verwendet.

FadeGrayScaleToColor() kann zwei grundlegend verschiedene Ausgangssituationen abdecken:

  • Unterstützt das Ausgabegerät logische Paletten, so erfolgt eine Palettenanimation.
  • Unterstützt das Ausgabegerät keine logischen Paletten, muß eine modifizierte DIB wiederholt gezeichnet werden, um die Animation zu erzielen.

Funktion CreateReserved-Palette

Wird das Image zum ersten Mal auf dem Ausgabegerät gezeichnet, entsteht das Problem, wie das Farbmapping erhalten werden kann. Da das erste Bild eine Grauwertdarstellung ist, werden einige Farben beim Realisieren der Grauwertpalette unterschiedlich umgemappt, da die Farbtabelle sich von der logischen Palette unterscheidet. Werden die Farben remapped, so werden die originalen Rot-, Grün- und Blautöne auf die gleiche Farbe in der logischen Grauwert-Palette gemappt. Jede weitere Animation ist dann nicht mehr in der Lage, die Rot-, Grün- und Blauwerte zu restaurieren, so daß sie weiterhin die gleiche Farbe haben.

Um dieses Problem zu vermeiden, wird die Bitmap zunächst auf einen Speicherkontext unter Verwendung einer Farbpalette ausgegeben. Ist die Bitmap erst einmal auf dem DC ausgegeben worden, so ist eine einfache Animation durch Änderungen der logischen Platte möglich. Dazu werden die gleichen Palettenoperationen auf beide Ausgabekontexte durchgeführt, was ermöglicht, BitBlt() gefahrlos auf dem physikalischen Ausgabegerät einzusetzen, ohne daß die Farben umgemappt werden. Befindet sich die Bitmap erst einmal auf dem Zielkontext, erfolgt eine einfache Palettenanimation, bis alle Farben in der Palette mit denen der Bitmap übereinstimmen.

Die gezeigte Vorgehensweise funktioniert jedoch nur dann, wenn das Zielgerät Palettenoperationen unterstützt. Wenn das Gerät mehr als 256 Farben unterstützt, liegt ebenfalls keine logische Palette vor. In diesen Fällen muß die Farbinformation in der DIB wiederholt geändert und die DIB mit geänderter Farbtabelle angezeigt werden. Dies erfordert konsequenterweise mehr Rechenleistung als eine reine Palettenanimation.

Die Funktion FadeGrayScaleToColor() erwartet im Parameter pDC einen Zeiger auf den Ausgabekontext und in hDIB ein Handle auf die anzuzeigende DIB. XDest und yDest legen die linke obere Ecke im Zielkontext fest. Die Anzahl der Anzeigeschleifen gibt nLoops vor, wobei die Zeit zwischen den Schleifen in Millisekunden in nDelay steht.

Farbmapping

void FadeGrayScaleToColor(
    CDC *pDC,  HANDLE hDIB, 
    int xDest, int yDest, 
    int nLoops,int nDelay ) 
{
  CPalette pal;
  CPalette *pOldPalette;
  PALETTEENTRY peAnimate[256];
  PALETTEENTRY peGray[256];
  PALETTEENTRY peOriginal[256];

Zunächst werden die Eckdaten bestimmt.

  BITMAPINFO &bmInfo = *(LPBITMAPINFO)hDIB;
 
  int nColors = bmInfo.bmiHeader.biClrUsed ? 
                bmInfo.bmiHeader.biClrUsed : 
                  1 << bmInfo.bmiHeader.biBitCount;
 
 int nReservedColors = nColors > 236 ? 236 : nReservedColors;
 int nWidth = bmInfo.bmiHeader.biWidth;
 int nHeight = bmInfo.bmiHeader.biHeight;

Falls benötigt, wird als nächstes eine logische Platte erzeugt.

  if (pDC->GetDeviceCaps(RASTERCAPS) & RC_PALETTE && 
      nColors <= 256 )
  {
   HPALETTE hPal = CreateReservedPalette(hDIB);
   pal.Attach( hPal );

Nach dem Speichern der Ausgangsfarben können nun die Grauwerte gesetzt werden.

  pal.GetPaletteEntries(0, nReservedColors, 
                        (LPPALETTEENTRY)&peGray);
  for( int i=0; i < nReservedColors; i++)
  {
   peOriginal[i].peRed =   peGray[i].peRed ;
   peOriginal[i].peGreen = peGray[i].peGreen;
   peOriginal[i].peBlue =  peGray[i].peBlue ;
   long lSquareSum = peGray[i].peRed   * peGray[i].peRed +
                     peGray[i].peGreen * peGray[i].peGreen +
                     peGray[i].peBlue  * peGray[i].peBlue;
   int nGray = (int)sqrt(((double)lSquareSum)/3);
   
   peGray[i].peRed = nGray;
   peGray[i].peGreen = nGray;
   peGray[i].peBlue = nGray;
  }

Die so erzeugte Palette kann nun in den Ausgabekontext selektiert und realisiert werden.

  pOldPalette = pDC->SelectPalette(&pal, FALSE);
  pDC->RealizePalette();

Unter Zuhilfenahme eines Speicherkontextes wird die Bitmap ohne Farbumsetzung gezeichnet.

  CDC memDC;
  memDC.CreateCompatibleDC( pDC );
  CBitmap bmp;
  bmp.CreateCompatibleBitmap( pDC, nWidth, nHeight );
  CBitmap *pOldBitmap = memDC.SelectObject( &bmp );
  CPalette *pOldMemPalette = memDC.SelectPalette(&pal, FALSE);
  memDC.RealizePalette();

Das Zeichnen der Bitmap in den Speicherkontext erfolgt über die Aufrufe:

  LPVOID lpDIBBits = (LPVOID)(bmInfo.bmiColors + nColors);
  
  ::SetDIBitsToDevice(memDC.m_hDC, 0, 0, nWidth, nHeight, 0, 0, 0,
                      nHeight, lpDIBBits, (LPBITMAPINFO)hDIB, 
                      DIB_RGB_COLORS);

Nach dem nachfolgenden Setzen der Farbe auf Grauwerte kann die Bitmap vom Speicherkontext in den Ausgabekontext kopiert werden. Hierbei findet kein Farbmapping statt, da in beiden Kontexten die Paletten identisch sind, so daß die Bitmap in der Startfarbe erscheint.

  AnimatePalette(hPal, 0, nColors, (LPPALETTEENTRY)&peGray);
  pDC->BitBlt(xDest, yDest, nWidth, nHeight, &memDC,0,0,SRCCOPY );

Es erfolgt nun die Übernahme der Grauwerteinträge in die Animationspalette sowie das Zurückselektieren der alten Objekte in den Speicherkontext.

  pal.GetPaletteEntries(0, nColors, (LPPALETTEENTRY)&peAnimate);
 
  memDC.SelectPalette(pOldMemPalette, FALSE);
  memDC.SelectObject( pOldBitmap );

Jetzt kann die Palettenanimation erfolgen, um die Bitmap in Farben anzuzeigen.

  for( i=1; i <= nLoops; i++ )
  {
   for (int j = 0; j< nColors; j++) 
   {  
    peAnimate[j].peRed = peGray[j].peRed - 
              ((peGray[j].peRed -peOriginal[j].peRed)*i)/nLoops;
    peAnimate[j].peGreen = peGray[j].peGreen -          
              ((peGray[j].peGreen-peOriginal[j].peGreen)*i)
    peAnimate[j].peBlue = peGray[j].peBlue  - 
              ((peGray[j].peBlue -peOriginal[j].peBlue)*i)/nLoops;
    peAnimate[j].peFlags = peGray[j].peFlags;
   }
   pal.AnimatePalette(0, nColors, (LPPALETTEENTRY)&peAnimate);
   Sleep(nDelay);
  }
  pDC->SelectPalette(pOldPalette, FALSE);
 }

Unterstützt das Ausgabegerät keine Paletten, aber besitzt die Bitmap eine Farbtabelle, kann die Farbtabelle direkt modifiziert werden. Hierzu werden in einer Schleife zunächst die Ausgabewerte gesichert, dann die Werte berechnet und zuletzt die Referenzfarbtabelle erzeugt.

 else if( (pDC->GetDeviceCaps(RASTERCAPS) & RC_PALETTE) == 0 && 
           nColors <= 256 )
 {
  for( int i=0; i < nColors; i++)
  {
   peOriginal[i].peRed =   bmInfo.bmiColors[i].rgbRed ;
   peOriginal[i].peGreen = bmInfo.bmiColors[i].rgbGreen;
   peOriginal[i].peBlue =  bmInfo.bmiColors[i].rgbBlue ;
 
   long lSquareSum = bmInfo.bmiColors[i].rgbRed *
                     bmInfo.bmiColors[i].rgbRed +
                     bmInfo.bmiColors[i].rgbGreen *
                     bmInfo.bmiColors[i].rgbGreen +
                     bmInfo.bmiColors[i].rgbBlue *
                     bmInfo.bmiColors[i].rgbBlue;
   int nGray = (int)sqrt(((double)lSquareSum)/3);
   bmInfo.bmiColors[i].rgbRed = nGray;
   bmInfo.bmiColors[i].rgbGreen = nGray;
   bmInfo.bmiColors[i].rgbBlue = nGray;
 
   peGray[i].peRed = nGray;
   peGray[i].peGreen = nGray;
   peGray[i].peBlue = nGray;
 }

Jetzt kann die Grauwertbitmap ausgegeben werden...

  LPVOID lpDIBBits = (LPVOID)(bmInfo.bmiColors + nColors);
  ::SetDIBitsToDevice(pDC->m_hDC, xDest, yDest, nWidth, nHeight, 
                      0, 0, 0, nHeight, lpDIBBits, 
                      (LPBITMAPINFO)hDIB, DIB_RGB_COLORS);

...und anschließend die Farbbitmap:

  for( i=1; i <= nLoops; i++ )
  {
   for (int j = 0; j< nColors; j++) 
   {  
    bmInfo.bmiColors[j].rgbRed   = peGray[j].peRed  - 
             ((peGray[j].peRed -peOriginal[j].peRed)*i)/nLoops;
    bmInfo.bmiColors[j].rgbGreen = peGray[j].peGreen - 
             ((peGray[j].peGreen-peOriginal[j].peGreen)*i)/nLoops;
    bmInfo.bmiColors[j].rgbBlue  = peGray[j].peBlue  - 
             ((peGray[j].peBlue -peOriginal[j].peBlue)*i)/nLoops;
   }

Abschließend wird die Bitmap noch einmal ausgegeben.

   ::SetDIBitsToDevice(pDC->m_hDC, xDest, yDest, nWidth, nHeight, 
                       0, 0, 0, nHeight, lpDIBBits, 
                       (LPBITMAPINFO)hDIB, DIB_RGB_COLORS);
 
    Sleep(nDelay);
  }
 }

Für alle übrigen Situationen wird die Bitmap einfach so ausgegeben, wie sie ist.

 else
 {
  LPVOID lpDIBBits = (LPVOID)(bmInfo.bmiColors + nColors);
  ::SetDIBitsToDevice(pDC->m_hDC, xDest, yDest, nWidth, nHeight, 
                      0, 0, 0, nHeight, lpDIBBits, 
                      (LPBITMAPINFO)hDIB, DIB_RGB_COLORS);
 }
}

Funktion FadeGrayScale-ToColor

Der umgekehrte Weg ist weniger aufwendig, da hier von Anfang an eine Farbbitmap mit einer entsprechenden logischen Palette vorliegt, sofern das Ausgabegerät Paletten unterstützt.

Die Funktion FadeColorToGrayScale() zeichnet zunächst eine DIB als volles Farbbild und tauscht dann die Farben gegen Grauwerte aus. Die Parameter dieser Funktion entsprechen denen der vorangegangenen Funktion FadeGrayScaleToColor().

Lösung B

void FadeColorToGrayScale(
   CDC *pDC, HANDLE hDIB, 
   int xDest, int yDest, 
   int nLoops,int nDelay ) 
{
 CPalette pal;
 CPalette *pOldPalette;
 PALETTEENTRY peAnimate[256];
 PALETTEENTRY peGray[256];
 PALETTEENTRY peOriginal[256];

Am Anfang steht die Auswertung der Bitmapeckdaten.

 BITMAPINFO &bmInfo = *(LPBITMAPINFO)hDIB ;
 
 int nColors = bmInfo.bmiHeader.biClrUsed ? 
               bmInfo.bmiHeader.biClrUsed : 
                  1 << bmInfo.bmiHeader.biBitCount;
 
 int nReservedColors = nColors > 236 ? 236 : nReservedColors;
 int nWidth = bmInfo.bmiHeader.biWidth;
 int nHeight = bmInfo.bmiHeader.biHeight;

Unterstützt das Ausgabegerät logische Paletten, wird eine Palette erzeugt und zugewiesen.

 if( pDC->GetDeviceCaps(RASTERCAPS) & 
     RC_PALETTE && nColors <= 256 )
 {
  HPALETTE hPal = CreateReservedPalette(hDIB);
  pal.Attach( hPal );

Die Palette kann jetzt aufgebaut werden, wobei zunächst die Originalfarben gesichert und dann die Grauwertwertfarben erzeugt werden.

  pal.GetPaletteEntries(0, nReservedColors, 
                        (LPPALETTEENTRY)&peOriginal);
  for( int i=0; i < nReservedColors; i++)
  {
   long lSquareSum = peOriginal[i].peRed *
                     peOriginal[i].peRed +
                     peOriginal[i].peGreen *
                     peOriginal[i].peGreen +
                     peOriginal[i].peBlue *
                     peOriginal[i].peBlue;
   int nGray = (int)sqrt(((double)lSquareSum)/3);
peGray[i].peRed = nGray;
   peGray[i].peGreen = nGray;
   peGray[i].peBlue = nGray;
  }

Die so erzeugte Palette kann in den Ausgabekontext selektiert und die Bitmap erstmals gezeichnet werden.

  pOldPalette = pDC->SelectPalette(&pal, FALSE);
  pDC->RealizePalette();
 
  LPVOID lpDIBBits = (LPVOID)(bmInfo.bmiColors + nColors);
  
  ::SetDIBitsToDevice(pDC->m_hDC, xDest, yDest, nWidth, nHeight, 
                      0, 0, 0, nHeight, lpDIBBits, 
                      (LPBITMAPINFO)hDIB, DIB_RGB_COLORS);

Über die nachfolgende doppelte for-Schleife wird die Farbbitmap dann schrittweise bis hin zur Grauwertdarstellung animiert.

  for( i=1; i <= nLoops; i++ )
  {
   for (int j = 0; j< nColors; j++) 
   {  
    peAnimate[j].peRed = peOriginal[j].peRed - 
             ((peOriginal[j].peRed -peGray[j].peRed)*i)/nLoops;
    peAnimate[j].peGreen = peOriginal[j].peGreen - 
             ((peOriginal[j].peGreen-peGray[j].peGreen)*i)/nLoops;
    peAnimate[j].peBlue = peOriginal[j].peBlue  - 
             ((peOriginal[j].peBlue -peGray[j].peBlue)*i)/nLoops;
 
    peAnimate[j].peFlags = peOriginal[j].peFlags;
   }
pal.AnimatePalette(0, nColors, (LPPALETTEENTRY)&peAnimate);
 
   Sleep(nDelay);
  }
pDC->SelectPalette(pOldPalette, FALSE);
 }

Wenn das Ausgabegerät keine logischen Paletten unterstützt, die Bitmap aber eine Farbtabelle besitzt, kann die Farbtabelle direkt modifiziert werden, um den Animationseffekt zu erzielen. Dazu wird zunächst die Farbtabelle aufgebaut.

FadeGrayScale-ToColor

else if( (pDC->GetDeviceCaps(RASTERCAPS) & RC_PALETTE) == 0 && nColors <= 256 )
 {
  for( int i=0; i < nColors; i++)
  {
   peOriginal[i].peRed =   bmInfo.bmiColors[i].rgbRed ;
   peOriginal[i].peGreen = bmInfo.bmiColors[i].rgbGreen;
   peOriginal[i].peBlue =  bmInfo.bmiColors[i].rgbBlue ;
long lSquareSum = bmInfo.bmiColors[i].rgbRed *
                     bmInfo.bmiColors[i].rgbRed +
                     bmInfo.bmiColors[i].rgbGreen *
                     bmInfo.bmiColors[i].rgbGreen +
                     bmInfo.bmiColors[i].rgbBlue *
                     bmInfo.bmiColors[i].rgbBlue;
   int nGray = (int)sqrt(((double)lSquareSum)/3);
peGray[i].peRed = nGray;
   peGray[i].peGreen = nGray;
   peGray[i].peBlue = nGray;
  }

Die Ausgabe des ersten (Farb-)Bildes erfolgt dann über die Aufrufsequenz.

  LPVOID lpDIBBits = (LPVOID)(bmInfo.bmiColors + nColors);
  ::SetDIBitsToDevice(pDC->m_hDC, xDest, yDest, nWidth, nHeight,  
                      0, 0, 0, nHeight, lpDIBBits, 
                      (LPBITMAPINFO)hDIB, DIB_RGB_COLORS);

Anschließend kann die Bitmap in einer Schleife auf Grauwerte geblendet werden, indem die Farbtabelle angepaßt und die Bitmap erneut ausgegeben wird.

  for( i=1; i <= nLoops; i++ )
  {
   for (int j = 0; j< nColors; j++) 
   {  
    bmInfo.bmiColors[j].rgbRed   = peOriginal[j].peRed  - 
             ((peOriginal[j].peRed -peGray[j].peRed)*i)/nLoops;
    bmInfo.bmiColors[j].rgbGreen = peOriginal[j].peGreen - 
             ((peOriginal[j].peGreen-peGray[j].peGreen)*i)/nLoops;
    bmInfo.bmiColors[j].rgbBlue  = peOriginal[j].peBlue  - 
             ((peOriginal[j].peBlue -peGray[j].peBlue)*i)/nLoops;
   }
  ::SetDIBitsToDevice(pDC->m_hDC, xDest, yDest, nWidth, nHeight, 
                      0, 0, 0, nHeight, lpDIBBits, 
                      (LPBITMAPINFO)hDIB, DIB_RGB_COLORS);

Die Verzögerung erfolgt wie gehabt am Ende der Schleife über Sleep().

   Sleep(nDelay);
  }
 }

Für alle übrigen Situationen wird die Bitmap einfach so ausgegeben, wie sie ist.

 else
 {
  LPVOID lpDIBBits = (LPVOID)(bmInfo.bmiColors + nColors);
  ::SetDIBitsToDevice(pDC->m_hDC, xDest, yDest, nWidth, nHeight,  
                      0, 0, 0, nHeight, lpDIBBits, 
                      (LPBITMAPINFO)hDIB, DIB_RGB_COLORS);
 }
}




Sachgebiet


© 2009-2012 by Alojado Publishing. Alle Rechte vorbehalten. Ausgewiesene Marken gehören ihren jeweiligen Eigentümern.
Mit der Benutzung dieser Seite erkennen Sie die Nutzungsbedingungen und die Datenschutzerklärung an. Der Betreiber übernimmt keine Haftung für den Inhalt verlinkter externer Internetseiten.
Seite erzeugt 2012-05-20 02:05:22 von textarchiv.alojado.de