How to make a Screen Mate:

Animated Transparent Windows  v.1.2

Since I have published smallGoblin in spring 2000, there were many letters with questions - how to create such software?  So, here is a short overview, to help those, who spend time, creating useless & crazy programs.     

The most important problem for Screen Mate is - how to make a window transparent

There seems to be 4 ways to do this in MS Windows:

  1. Redrawing background manually
  2. Win32 Regions
  3. DirectDraw Overlays
  4. Microsoft Agent 

See also the Conclusions

1. Redrawing background manually 

This strategy was used by almost all old Screen Mates, including famous Scam Poo.  It is still popular among some developers.

The trick is to receive a part of a desktop, which is covered by Screen Mate's window, as an image. And then draw it in the Screen Mate's window. Then to draw in the same window the image of the Screen Mate, without changing pixels, which are transparent. And it'll look like the image is transparent. 

I have also heard, that if standard WM_ERASEBKGND (or smth. like this ;-)) event handler for window is overrided with the empty function (returning proper value), we'll have the same effect. But I failed to do this (frankly speaking I haven't tried much).   

The advantage of this technology is that it works on any Windows - from 3.1 till 2000 (I guess so :)). It is also quite fast.  

The disadvantage is big - background is redrawing not very good. Especially when user opens, closes, moves windows behind the Screen Mate - the Mate does not redrawing background quickly enough, the image twinkles, etc. 

Essentially, there is not much known for me about this technique, 'cause we selected Win32 regions as an engine for smallGoblin.

2. Win32 Regions

2.1 Transparent window

This is a legal way to make transparent window in MS Windows (since Windows 95). Here is the code:

--------------- [cut] -----------
//  Defining variables
HRGN rgn1, rgn2;     // regions

HWND hwnd1;          // window handle

//  Create any regions with the standard Win32 function (CreateRectRgn, CreateEllipticRgn, CreatePolygonRgn, etc.):
rgn1 = CreateEllipticRgn(0,0,10,10);  // create circle 1

rgn2 = CreateEllipticRgn(5,5,20,20);  // create circle 2

//  Combine a complex region from simple regions
CombineRgn(rgn1,rgn1,rgn2,RGN_OR);    // combine two circles

// Delete useless object
DeleteObject(rgn2);                   // delete useless region of circle 1

hwnd1 = ...   ;  // hwnd1 must point to the form window, which will be transparent


// Set the window region of a window Handle
if ( rgn1 != 0 ) SetWindowRgn( hwnd1 , rgn1, true ); // applying region to the window
--------------- [cut] -----------

2.2  Transparent image

Now we can make transparent windows. Ok, but how to create a region for the specific image? And to make it transparent for a special color in the image? Well, I don't know the excellent solution. It's possible to create an array of  points of polygon which describes the image, and use  CreatePolygonRgn() Win32 function to create a polygonal region.  Or to use the algorithm of smallGobiln. It is divided into 2 parts (for quick dynamic animation. see the next section). First part creates a temporary array of rectangles (rects) on program start. The second part creates a region from this array with CreateRectRgn() / CombineRgn()  Win32 functions (dynamically on every window repaint).
The main idea is simple. Function scans the image, pixel by pixel, creates a small rectangular regions for every not-transparent pixel & combine them into 1 main region.  The smallGoblin algorithm is just the improved version of this idea. We are creating regions not for pixels, but for vertical lines. We also can reduce the number of lines by combining them into rectangles (PackRectListV()) when it is possible.

Here are corresponding functions from  smallGoblin  code (C++Builder 3):

--------------- [cut] -----------

#define iSprites 36       // we have 36 sprites

unsigned char RegionRects[iSprites][4][76];    // 36 sprites, 
                                               // 4 coordinates (left,top,right,bottom), 76 maximum rects
unsigned char NumberOfRects[iSprites];         // number of rects in every array

....

// Create an array of rects from an image in Can; image rect= R; store info in a global array with 
// Id = iSprite
void CreateRectListV( TCanvas* Can, TRect* R, int iSprite)
{

 int X,Y,Ye ;         //  picture - scanning variables
 int clTransparent;   //  transparent color
 int iRects = 0;      //  rects counter

 X = R->Left;         //  start  scanning from left-top cornen
 Y = R->Top;

 clTransparent = Can->Pixels[X][Y]; // First top-left point is considered to be
                                   //  a transparent color
 do
 {
  // take first point
  while (Can->Pixels[X][Y] == clTransparent )
  {
   Y++ ;
   if ( Y >R->Bottom )
    {
     Y = R->Top; X++;
     if ( X >R->Right ) break;
    }
  }

 if ( X >R->Right ) break;
 // take end point
 Ye = Y;
 while (Can->Pixels[X][Ye] != clTransparent )
 {
  Ye++;
  if ( Ye >R->Bottom ) break;
 }

// now storing new rect in a global array
 frmGoblin->RegionRects[iSprite][0][iRects] = X -R->Left;
 frmGoblin->RegionRects[iSprite][1][iRects] = Y -R->Top;
 frmGoblin->RegionRects[iSprite][2][iRects] = X -R->Left;
 frmGoblin->RegionRects[iSprite][3][iRects] = Ye - 1 -R->Top;
 iRects++;
 if (iRects>75)
 {
  ShowMessage("Internal Error - Too many rects " + IntToStr(iSprite));
  break;
 }
 if (Ye >R->Bottom ) { Ye = R->Top; X++; }
 Y = Ye;
 }
 while ( X <= R->Right );
 // storing the number of rects, in the global array
 frmGoblin->NumberOfRects[iSprite] = iRects;
}

//-------------------------------------------------------------------------
// This function pack all rects (to near rects are converted to 1 rect), 
// so that they'll take less place
// returns how many rectangles saved, in %
int PackRectListV( )
{
 -- skipped --   

}

//-------------------------------------------------------------------------
// This function dynamically creates a region from rects array
//  iSprite - ID of the rect array to use
HRGN CreateRgnFormRects( int iSprite)
{
 HRGN h1,h2;

 int iRects = frmGoblin->NumberOfRects[iSprite];
 if ( iRects < 1 ) return 0;

 h1 = CreateRectRgn(frmGoblin->RegionRects[iSprite][0][0],
 frmGoblin->RegionRects[iSprite][1][0],
 frmGoblin->RegionRects[iSprite][2][0]+1,
 frmGoblin->RegionRects[iSprite][3][0]+1);

 if ( iRects == 1 ) return h1;

 for ( int i = 1; i < iRects; i++ )
 {
  h2 = CreateRectRgn(frmGoblin->RegionRects[iSprite][0][i],
 frmGoblin->RegionRects[iSprite][1][i],
 frmGoblin->RegionRects[iSprite][2][i]+1,
 frmGoblin->RegionRects[iSprite][3][i]+1);
  CombineRgn(h1,h1,h2,RGN_OR);
  DeleteObject(h2);
 }
 return h1;
}
--------------- [cut] -----------

2.3  Animated transparent image

That's the most difficult part, 'cause I haven't found how to store pre-generated regions somewhere. Of course, you can say - it is possible to create many windows with different images & different regions, and to show only 1 at a time. Hmmm... but how much resources this will take, if we have, f.e. 36 images?  And if we try to run 10 Goblins? We'll have 360 windows.... %)  No! That's not our way.  The Goblin do this: 1. Create a temporary array of rects for every sprite at program start. And then creates new region from this array on EVERY WINDOW REPAINT... This also sounds horrible, but it works more or less well. Here is the code (C++Builder 3):

--------------- [cut] -----------
On Goblin start-up we generate arrays of rects for all images:

...........

// Generating rects arrays for all sprites.
// Sprites are stored in a 1 big image (iGoblins) - 9 pictures width, 4 pictures height
// isGetTop(), isGetLeft()  - are just functions, which returns correct coordinates in the big image

for ( int i = 0; i < iSprites; i++ )
{

  CreateRectListV(iGoblins->Canvas,
    &Rect( isGetLeft(i),                 // GWidth * ( i % 10 ) + GAddX,
    isGetTop(i),                         // GHeight * ( i / 10 ) + GAddY,
    isGetLeft(i) + GWidth-2,             //GWidth * ( ( i % 10 ) + 1 ) - 1 + GAddX,
    isGetTop(i) + GHeight-2              //GHeight * ( ( i / 10 ) + 1 ) + GAddY
            ),i );
}

PackRectListV();   // making big rects from many small rects where possible. 

...........
Then, we can use this code to draw current Goblin sprite:

rgn = CreateRgnFormRects( iSprite );  // Now!!! We are generating a Region!

if ( rgn != 0 ) SetWindowRgn( Handle, rgn, true ); // Handle is a Goblin
                       // main window handle

// Drawing the current sprite image to the window

frmGoblin->Canvas->CopyRect( Rect(0, 0, GWidth, GHeight),
           iGoblins->Canvas,
            Rect( isGetLeft(Sprite), // X * GWidth + GAddX,
            isGetTop(Sprite),//Y * GHeight + GAddY,
            isGetLeft(Sprite)+GWidth,//( X + 1 ) * GWidth + GAddX,
            isGetTop(Sprite)+GHeight//( Y + 1 ) * GHeight + GAddY
            ));
--------------- [cut] -----------

Seems that's all.  Hope I described this, hmmm..., not so bad...
Anyway - things are not so bad! :))) I wrote a a simple example, based on smallGoblin drawing engine. Take it here (exe+src)
and enjoy!

Try the fortune on http://msdn.microsoft.com  - here may be some explanations of regions conception from the creators. :) 

By the way! There is a nice Screen Mate Wind-Up Bird with Visual Basic src at http://www.cwinapp.com (look in Fun Stuff section). This Mate also uses SetWindowRgn()...

3. DirectDraw Overlays

There is an example from DirectX SDK (I saw it in DirectX 7.0 SDK):  
Mosquito DirectDraw Sample
This seems to be very good solution for Screen Mate development. The only limitation is DirectX, that must be installed on the system. As far as I understood, it uses features of DirectX 5.    So - this will not work on pure Windows95 and Windows NT 4 (without DirectX manually installed). But this may work without any problems on Windows 98 & Windows 2000.

4. Microsoft Agent

We - those, who are making screen mates, are not alone in the galaxy.  Bill Gates & his crew made something in this sphere too.  They called the creature "Microsoft Agent". We can see it, for example, in MS Office2000 as "Office Assistants".  So - what is it?

Microsoft Agent is an ActiveX component...  What does it means? Hmmm...

It's necessary to install this component. (take executables from Microsoft & run them). Then we may have: 1) Microsoft Agent ActiveX engine 2) TrueVoice speak engine 3) Tool for editing *.asc files (sprites for the character).  Now we can take out favorite programming language, import "Microsoft Agent" ActiveX library & enjoy using it. 

Here is an sample HTML page, which controlls MS Agent with VBScript (it's possible to write MS Agent controllers as HTML of WSH (Windows Scripting Host) files, using JScript or VBScript). The following page is from MS INet SDK. If you have MS Agent installed, you may see and hear smth.:

Microsoft Agent Sample Page    

Conclusions

Technology Speed Program Size Run at Examples Mouse Hit Transparency
Redrawing background manually  normal small Win 3.x, 9x, NT, 2000 Scam Poo, Message Mates No
Win32 Regions slow-normal small Win 9x, NT, 2000 :) smallGoblin, Wind-Up Bird Yes
DirectDraw Overlays fast hmmm... seems small Win98, 2000 or other, if DirectX installed Mosquito No
Microsoft Agent fast big (asc file is big; code may be small) Win9x, NT, 2000 + MS Agent ActiveX component installed MS Office Assistants Yes



You can contact me at: smallgoblin@yahoo.com
Homepage is:  http://smallgoblin.narod.ru

12.12.2000 (c) Kane+
14.12.2000 v.1.2

Top

 

Hosted by uCoz