Sample Project:  True Transparency with TBitmaps
By: David Richards



David Richards has kindly submitted the following article to fill in some of the cracks that the VCL documentation has left out.  Specifically, he discusses the limitations of using the TBitmap::TransparentMode and TransparentColor properties.  Dave shows us how to overcome these limitations by generating a mask using the BitBlt API function.

   The setting of TBitmap::TransparentColor and TBitmap::TransparentMode to a value doesn't make that color transparent in the bitmap - you can't overlay it over an image and have that color be invisible. (All it does is control the value the TBitmap::TransparentColor contains for reference if the image is to be drawn as an icon, I suspect.)  If you think about it, it's pretty obvious that such a simplistic approach won't always work - unless the CopyRect (or BitBlt or whatever) function did some pretty fancy image processing, it can't pick a color that will preserve all background colors when merging the images.

    Oftentimes, developers paint the images on a black background and BitBlt in SRCPAINT mode, or use a white background and BitBlt in SRCAND mode.  Again, if you think about it, it's obvious why this works - you're either OR'ing with a field full of zeros, preserving anything that isn't black, or you're AND'ing with a field of ones, again preserving the colors other than the background.

    The problem comes if you want to do transparent overlays on a background other than these two colors. If you BitBlt with a color/copy mode combination that will preserve the background colors, the colors drawn on the bottom surface are altered by the overlay - it's not truly transparent.  You can use a TImageList with its various masked drawing routines, however, these are significantly slower than the solution presented below.  Such performance overhead can accumulate for graphics extensive applications.

   The solution is to generate a mask. You draw the image you want to superimpose in the color you want on a white background (all ones),  create a mask bitmap with a black background (all zeroes) and draw the image you want to overlay in white. You then OR (SRCPAINT)  the mask with the base image. The colors in the part of the image you want to preserve are unchanged, but whatever is beneath the white section of the mask is OR'ed to ones.  Next, AND the overlay (SRCAND) onto the base. As the overlay background is all ones, the colors on the base image are once again preserved, but the colors on the overlaid image that you want to superimpose are AND'ed with the white area generated by the mask, preserving THEIR colors.

    So I made up a little demo project. Pretty basic, but it shows how it works. You can successfully superimpose stuff when both images are on a black background if you use SRCPAINT, and you can successfully superimpose with white backgrounds using SRCAND. Any other color backgrounds, though, even when they're matched, will interfere with the object colors unless you use the mask method. With the mask method background colors don't matter (you override them anyway), and the overlay is truly transparent.

- Dave
 

//in header...

private:
   bool draw;
   TColor Color[4];

   // the images
   Graphics::TBitmap *BaseImage,*OverlayImage,*Mask;

   // canvasses for the displays
   TControlCanvas *BaseCanvas,*OverlayCanvas,*CompositeCanvas;

   // used to maintain image
   void __fastcall OnIdle(TObject *Sender,bool &Done);
 

   void DrawImages();  // Updates base and overlay
   void AndBlt();      // the copy functions
   void PaintBlt();
   void MaskedBlt();
 
 
 


 
 
 

//---------------------------------------------------------------------------
//in source...

void __fastcall TBltDemo::FormCreate(TObject *Sender)
{
   BaseImage = new Graphics::TBitmap;     // instantiate stuff
   BaseImage->Height = 160;
   BaseImage->Width = 160;

   OverlayImage = new Graphics::TBitmap;
   OverlayImage->Height = 160;
   OverlayImage->Width = 160;

   Mask = new Graphics::TBitmap;
   Mask->Height = 160;   // all display panels and bitmaps are 160x160
   Mask->Width = 160;

   BaseCanvas = new TControlCanvas;
   BaseCanvas->Control = Base;

   OverlayCanvas = new TControlCanvas;
   OverlayCanvas->Control = Overlay;

   CompositeCanvas = new TControlCanvas;
   CompositeCanvas->Control = Composite;

   Color[0] = clBlack;           // base background color
   Color[1] = clRed;             // base object color
   Color[2] = clBlack;           // overlay background color
   Color[3] = clBlue;            // overlay object color

   Application->OnIdle = &OnIdle;
}
//---------------------------------------------------------------------------
void __fastcall TBltDemo::FormDestroy(TObject *Sender)
{
   delete BaseImage;
   delete OverlayImage;
   delete Mask;

   delete BaseCanvas;
   delete OverlayCanvas;
   delete CompositeCanvas;
}
//---------------------------------------------------------------------------
void __fastcall TBltDemo::OnIdle(TObject*, bool& done)
{
   done = true;

   if (draw == true)
   {
      draw = false; // clear draw flag

      BaseBkgndBlob->Color = Color[0];
      BaseObjctBlob->Color = Color[1];
      OvrlyBkgndBlob->Color = Color[2];
      OvrlyObjctBlob->Color = Color[3];

      DrawImages();
   }
}
//---------------------------------------------------------------------------
void __fastcall TBltDemo::FormActivate(TObject *Sender)
{
   draw = true;
}
//---------------------------------------------------------------------------
void __fastcall TBltDemo::FormPaint(TObject *Sender)
{
   draw = true;
}

//---------------------------------------------------------------------------
// Button Event Handlers
//---------------------------------------------------------------------------
void __fastcall TBltDemo::BaseBkgndBtnClick(TObject *Sender)
{
   ColorDialog->Color = Color[0];
   if (ColorDialog->Execute())
   {
      Color[0] = ColorDialog->Color;
      draw = true;
   }
}
//---------------------------------------------------------------------------

void __fastcall TBltDemo::BaseObjctClick(TObject *Sender)
{
   ColorDialog->Color = Color[1];
   if (ColorDialog->Execute())
   {
      Color[1] = ColorDialog->Color;
      draw = true;
   }

}
//---------------------------------------------------------------------------

void __fastcall TBltDemo::OvrlyBkgndBtnClick(TObject *Sender)
{
   ColorDialog->Color = Color[2];
   if (ColorDialog->Execute())
   {
      Color[2] = ColorDialog->Color;
      draw = true;
   }

}
//---------------------------------------------------------------------------

void __fastcall TBltDemo::OvrlyObjctBtnClick(TObject *Sender)
{
   ColorDialog->Color = Color[3];
   if (ColorDialog->Execute())
   {
      Color[3] = ColorDialog->Color;
      draw = true;
   }
}

//---------------------------------------------------------------------------
// Draw and Blt routines
//---------------------------------------------------------------------------
void TBltDemo::DrawImages()
{
   BaseImage->Canvas->Brush->Color = Color[0];   // fill in base background
   BaseImage->Canvas->Pen->Color = Color[0];
   BaseImage->Canvas->Rectangle(0,0,Base->Height,Base->Width);

   BaseImage->Canvas->Brush->Color = Color[1];  // draw ellipse in base image
   BaseImage->Canvas->Pen->Color = Color[1];
   BaseImage->Canvas->Ellipse(10,10,70,150);

   OverlayImage->Canvas->Brush->Color = Color[2]; // fill in overlay background
   OverlayImage->Canvas->Pen->Color = Color[2];
   OverlayImage->Canvas->Rectangle(0,0,Overlay->Height,Overlay->Width);

   OverlayImage->Canvas->Brush->Color = Color[3];
   OverlayImage->Canvas->Pen->Color = Color[3];

   POINT points[3];
   points[0] = Point(120,10);                // draw triangle in overlay
   points[1] = Point(85,90);
   points[2] = Point(150,150);

   OverlayImage->Canvas->Polygon(points, 2);
 

   // blt 2 component images to their displays using copy mode
   BitBlt(BaseCanvas->Handle,0,0,160,160,
          BaseImage->Canvas->Handle,0,0,SRCCOPY);
   BitBlt(OverlayCanvas->Handle,0,0,160,160,
          OverlayImage->Canvas->Handle,0,0,SRCCOPY);
 

   if (PaintBtn->Checked == true)
      PaintBlt();
   else if (AndBtn->Checked == true)
      AndBlt();
   else if (MaskedBtn->Checked == true)
      MaskedBlt();

}

//---------------------------------------------------------------------------
void TBltDemo::PaintBlt()
{
   BitBlt(CompositeCanvas->Handle,0,0,160,160,  // copy base image to canvas
          BaseImage->Canvas->Handle,0,0,SRCCOPY); // using copy mode

   BitBlt(CompositeCanvas->Handle,0,0,160,160,  // superimpose overlay image
          OverlayImage->Canvas->Handle,0,0,SRCPAINT);  // using SRCPAINT

}

//---------------------------------------------------------------------------

void TBltDemo::AndBlt()
{
   BitBlt(CompositeCanvas->Handle,0,0,160,160,  // copy base image to canvas
          BaseImage->Canvas->Handle,0,0,SRCCOPY); // using copy mode

   BitBlt(CompositeCanvas->Handle,0,0,160,160,  // superimpose overlay image
          OverlayImage->Canvas->Handle,0,0,SRCAND);  // using SRCAND

}

//---------------------------------------------------------------------------

void TBltDemo::MaskedBlt()
{
   BitBlt(CompositeCanvas->Handle,0,0,160,160,  // copy base image to canvas
          BaseImage->Canvas->Handle,0,0,SRCCOPY); // using copy mode
 

   OverlayImage->Canvas->Brush->Color = clWhite; // redraw overlay object on
   OverlayImage->Canvas->Pen->Color = clWhite;   // white background
   OverlayImage->Canvas->Rectangle(0,0,Overlay->Height,Overlay->Width);

   OverlayImage->Canvas->Brush->Color = Color[3];
   OverlayImage->Canvas->Pen->Color = Color[3];

   POINT points[3];
   points[0] = Point(120,10);                // draw triangle in overlay
   points[1] = Point(85,90);                 // in correct color
   points[2] = Point(150,150);

   OverlayImage->Canvas->Polygon(points, 2);
 

   Mask->Canvas->Brush->Color = clBlack; // fill mask with
   Mask->Canvas->Pen->Color = clBlack;   // black background
   Mask->Canvas->Rectangle(0,0,Mask->Height,Mask->Width);

   Mask->Canvas->Brush->Color = clWhite;
   Mask->Canvas->Pen->Color = clWhite;
                                           // draw triangle in white
   Mask->Canvas->Polygon(points, 2);

   BitBlt(CompositeCanvas->Handle,0,0,160,160,  // copy mask onto composite
          Mask->Canvas->Handle,0,0,SRCPAINT); // using SRCPAINT mode

   BitBlt(CompositeCanvas->Handle,0,0,160,160,  // copy overlay onto composite
          OverlayImage->Canvas->Handle,0,0,SRCAND); // using SRCAND mode

}
 
 

 


 
 

Download sample project: BltDemo.zip (155 KB)