Updated 2002-06-04 08:54:43

/* See <URL: http://wiki.tcl.tk/3440.html > for details */
 #include "DimgImage.h"

 char          *g_aszOptions[] = { "-height", "-width", "-mask", "-rgb", (char*)NULL };
 char          *g_aszModes[] = { "disable",
                                  "BLACKNESS", "DSTINVERT", "MERGECOPY", "MERGEPAINT", "NOTSRCCOPY", "NOTSRCERASE",
                                  "PATCOPY", "PATINVERT", "PATPAINT", "SRCAND", "SRCCOPY", "SRCERASE", "SRCINVERT", "SRCPAINT",
                                  "WHITENESS", (char*)NULL };
 DWORD          g_adwModes[] = { 0,
                                  BLACKNESS, DSTINVERT, MERGECOPY, MERGEPAINT, NOTSRCCOPY, NOTSRCERASE,
                                  PATCOPY, PATINVERT, PATPAINT, SRCAND, SRCCOPY, SRCERASE, SRCINVERT, SRCPAINT,
                                  WHITENESS };
 RGBQUAD        g_aDefaultColorTable[]
                        = {   0, 0, 0, 0,     0, 0, 255, 0,     0, 255, 0, 0,     0, 255, 255, 0,
                            255, 0, 0, 0,   255, 0, 255, 0,   255, 255, 0, 0,   255, 255, 255, 0 };

 Tk_ImageType   g_stDimgDesc;

 /*
  *----------------------------------------------------------------------
  *
  * DimgConfigureCmd --
  *
  *  This procedure is called when a Dimg image is created or
  *  reconfigured.  It processes configuration options and resets
  *  any instances of the image.
  *
  * Results:
  *  A standard Tcl return value.
  *
  * Side effects:
  *  Existing instances of the image will be redisplayed to match
  *  the new configuration options.
  *
  *----------------------------------------------------------------------

     -height    height of image widget
     -width    width of image widget
     -mask     mode for mask bitmap
     -rgb      mode for rgb bitmap
  */

 int DimgConfigureCmd( Tcl_Interp *pInterp, DimgMaster *pMaster, int nObj, Tcl_Obj * CONST aObj[] )
 {
   Tcl_Obj *CONST     *pObject;
   Tcl_Obj            *pResult = Tcl_GetObjResult(pInterp);
   BITMAPCOREHEADER    stBMCoreHeader;
   int                 iOption;
   int                 iMode;
   char                szBuffer[512];
   int                 iBuffer = 0;
   bool                bSizeChange = false;

   if ( nObj == 0 )
   {
     /*
       return all configuration parameters
      */
     iBuffer += sprintf( szBuffer, "{-height %d -width %d ", pMaster->nHeight, pMaster->nWidth );
     for ( iMode = 0; g_adwModes[iMode] != pMaster->dwImageMode; iMode++ );
     iBuffer += sprintf( szBuffer + iBuffer, "-rgb %s ", g_aszModes[iMode] );
     for ( iMode = 0; g_adwModes[iMode] != pMaster->dwMaskMode; iMode++ );
     iBuffer += sprintf( szBuffer + iBuffer, "-mask %s}", g_aszModes[iMode] );
     Tcl_AppendStringsToObj( pResult, szBuffer, NULL );
     return TCL_OK;
   }

   pObject = aObj;
   for( ; nObj >= 2; nObj -= 2, pObject += 2 )
   {
     if ( Tcl_GetIndexFromObj( pInterp, pObject[0], (const char**)g_aszOptions, "option", 0, &iOption ) != TCL_OK )
     {
       return TCL_ERROR;
     }
     switch( iOption )
     {
       case 0: // height
         if ( Tcl_GetIntFromObj( pInterp, pObject[1], &pMaster->nHeight ) != TCL_OK )
         {
           return TCL_ERROR;
         }
         bSizeChange = true;
         break;
       case 1: // width
         if ( Tcl_GetIntFromObj( pInterp, pObject[1], &pMaster->nWidth ) != TCL_OK )
         {
           return TCL_ERROR;
         }
         pMaster->nMaskPitch = ( ( ( pMaster->nWidth - 1 ) >> 2 ) + 1 ) << 2;
         pMaster->nImagePitch = pMaster->nWidth << 2;
         bSizeChange = true;
         break;
       case 2: case 3: // mask, rgb
         if ( Tcl_GetIndexFromObj( pInterp, pObject[1], (const char**)g_aszModes, "mode", 0, &iMode ) != TCL_OK )
         {
           return TCL_ERROR;
         }
         if ( iOption == 3 )
         {
           pMaster->dwImageMode = g_adwModes[iMode];
         }
         else
         {
           pMaster->dwMaskMode  = g_adwModes[iMode];
         }
         break;
     }
   }

   if ( ( bSizeChange || !pMaster->dwImageMode ) && pMaster->bmImage )
   {
     DeleteObject( pMaster->bmImage );
     pMaster->bmImage = NULL;
   }
   if ( ( bSizeChange || !pMaster->dwMaskMode ) && pMaster->bmMask )
   {
     DeleteObject( pMaster->bmMask );
     pMaster->bmMask = NULL;
   }

   if ( pMaster->nWidth && pMaster->nHeight )
   {
     stBMCoreHeader.bcHeight = pMaster->nHeight;
     stBMCoreHeader.bcWidth = pMaster->nWidth;
     stBMCoreHeader.bcPlanes = 1;

     if ( pMaster->dwImageMode && !pMaster->bmImage )
     {
       stBMCoreHeader.bcSize = sizeof(BITMAPCOREHEADER);
       stBMCoreHeader.bcBitCount = 32;
       pMaster->bmImage = CreateDIBSection( GetDC(0), (BITMAPINFO*)&stBMCoreHeader, DIB_RGB_COLORS, (void**)&pMaster->pImageData, 0, 0 );
       if ( !pMaster->bmImage )
       {
         Tcl_AppendResult( pInterp, "dimg image error: Can't allocate rgb bitmap", 0 ) ;
         return TCL_ERROR;
       }
     }
     if ( pMaster->dwMaskMode && !pMaster->bmImage )
     {
       stBMCoreHeader.bcSize = sizeof(BITMAPCOREHEADER);
       stBMCoreHeader.bcBitCount = 8;
       pMaster->bmMask  = CreateDIBSection( GetDC(0), (BITMAPINFO*)&stBMCoreHeader, DIB_PAL_COLORS, (void**)&pMaster->pMaskData, 0, 0 );
       if ( !pMaster->bmMask )
       {
         Tcl_AppendResult( pInterp, "dimg image error: Can't allocate mask bitmap", 0 ) ;
         return TCL_ERROR;
       }
     }
     GdiFlush();
   }

   Tk_ImageChanged( pMaster->tkMaster, 0, 0, pMaster->nWidth, pMaster->nHeight, pMaster->nWidth, pMaster->nHeight );

   return (TCL_OK);
 }

 /*
  *----------------------------------------------------------------------
  *
  * DimgCgetCmd --
  *
  *  Return one or all configuration options.
  *
  * Results:
  *  A standard Tcl return value.  If TCL_ERROR is returned then
  *  an error rawImgDisplayMessage is left in masterPtr->interp->result.
  *
  * Side effects:
  *  none
  *
  *----------------------------------------------------------------------

     -height    height of image widget
     -width    width of image widget
     -mask     mode for mask bitmap
     -rgb      mode for rgb bitmap
  */

 int DimgCgetCmd( Tcl_Interp *pInterp, DimgMaster *pMaster, int nObj, Tcl_Obj * CONST aObj[] )
 {
   char                szBuffer[512];
   int                 iOption;
   int                 iMode;
   DWORD               dwMode;

   if ( Tcl_GetIndexFromObj( pInterp, aObj[0], (const char**)g_aszOptions, "option", 0, &iOption ) != TCL_OK )
   {
     return TCL_ERROR;
   }
   switch( iOption )
   {
     case 0: // height
       sprintf( szBuffer, "%d", pMaster->nHeight );
       break;
     case 1: // width
       sprintf( szBuffer, "%d", pMaster->nWidth );
       break;
     case 2: case 3: // mask, rgb
       if ( iOption == 3 )
       {
         dwMode = pMaster->dwImageMode;
       }
       else
       {
         dwMode = pMaster->dwMaskMode;
       }
       for ( iMode = 0; g_adwModes[iMode] != dwMode; iMode++ );
       strcpy( szBuffer, g_aszModes[iMode] );
       break;
   }
   Tcl_AppendResult( pInterp, szBuffer, NULL );

   return TCL_OK;
 }

 /*
  *----------------------------------------------------------------------
  *
  * DimgFillOrOvalCmd --
  *
  *  Fills the entire area or the largest possible oval
  *  inside the rgb or mask image with a specifc color or index.
  *  The prototyped functions below do the actual work.
  *
  * Results:
  *  A standard Tcl return value.
  *
  * Side effects:
  *  none
  *
  *----------------------------------------------------------------------

   oval [rgb|mask] value
   fill [rgb|mask] value

  */

 void DimgFillRgb( int nX, int nY, int nPitch, int iColor,  char * pData );
 void DimgOvalRgb( int nX, int nY, int nPitch, int iColor,  char * pData );
 void DimgFillMask( int nX, int nY, int nPitch, int iColor, char * pData );
 void DimgOvalMask( int nX, int nY, int nPitch, int iColor, char * pData );

 int DimgFillOrOvalCmd( int iAction, Tcl_Interp *pInterp, DimgMaster *pMaster, int nObj, Tcl_Obj * CONST aObj[] )
 {
   int                 iResult;
   XColor              stColor;
   const char         *szColor;
   int                 iColor;
   static char        *aszTargets[] = { "rgb", "mask", (char*)NULL };
   int                 iTarget;

   if ( Tcl_GetIndexFromObj( pInterp, aObj[0], (const char **)aszTargets, "target", 0, &iTarget ) != TCL_OK )
   {
     return TCL_ERROR;
   }

   switch ( iTarget )
   {
   case 0: //rgb
     if ( !pMaster->bmImage )
     {
       Tcl_AppendResult( pInterp, "no rgb bitmap assigned", NULL );
       return TCL_ERROR;
     }
     szColor = Tcl_GetStringFromObj( aObj[1], 0 );
     iResult = XParseColor( 0, 0, szColor, &stColor );
     /* the first two arguments are not available here -
        but they are anyway currently not used in the
        implementation of XParseColor */
     if ( !iResult )
     {
       Tcl_AppendResult( pInterp, "invalid color name \"", szColor, "\"", NULL );
       return TCL_ERROR;
     }
     iColor = (stColor.blue >> 8) +
              (stColor.green & 0x00ff00 ) +
              ((stColor.red & 0x00ff00 ) << 8 );

     if ( iAction )
     {
       DimgOvalRgb( pMaster->nWidth, pMaster->nHeight, pMaster->nImagePitch, iColor, pMaster->pImageData );
     }
     else
     {
       DimgFillRgb( pMaster->nWidth, pMaster->nHeight, pMaster->nImagePitch, iColor, pMaster->pImageData );
     }
     break;

   case 1: //mask
     if ( !pMaster->bmMask )
     {
       Tcl_AppendResult( pInterp, "no mask bitmap assigned", NULL );
       return TCL_ERROR;
     }
     iResult = Tcl_GetIntFromObj( pInterp, aObj[1], &iColor );
     if ( iResult != TCL_OK )
     {
       return TCL_ERROR;
     }
     if ( iColor < 0 || iColor > 7 )
     {
       Tcl_AppendResult( pInterp, "color index out of range", NULL );
       return TCL_ERROR;
     }
     if ( iAction )
     {
       DimgOvalMask( pMaster->nWidth, pMaster->nHeight, pMaster->nMaskPitch, iColor, pMaster->pMaskData );
     }
     else
     {
       DimgFillMask( pMaster->nWidth, pMaster->nHeight, pMaster->nMaskPitch, iColor, pMaster->pMaskData );
     }
     break;
   }
   Tk_ImageChanged( pMaster->tkMaster, 0, 0, pMaster->nWidth, pMaster->nHeight, pMaster->nWidth, pMaster->nHeight );
   return TCL_OK;
 }

 void DimgOvalRgb( int nX, int nY, int nPitch, int iColor,  char * pData )
 {
   int     iX;
   int     iY;
   int     iX0;
   int     iX1;
   double  fA = (double) nX / 2.0;
   double  fB = (double) nY / 2.0;
   int    *piData;

   for ( iY = 0; iY < nY; iY++ )
   {
     iX0 = (int)( ( ( fB - sqrt( (double)iY * ( 2.0 * fB - (double)iY ) ) ) * fA ) / fB );
     iX1 = nX - iX0;
     piData = (int*)( pData + iY * nPitch );
     for ( iX = iX0; iX < iX1; iX++ )
     {
       piData[iX] = iColor;
     }
   }
 }

 void DimgFillRgb( int nX, int nY, int nPitch, int iColor,  char * pData )
 {
   int     iX;
   int     iY;
   int    *piData;

   for ( iY = 0; iY < nY; iY++ )
   {
     piData = (int*)( pData + iY * nPitch );
     for ( iX = 0; iX < nX; iX++ )
     {
       piData[iX] = iColor;
     }
   }
 }

 void DimgOvalMask( int nX, int nY, int nPitch, int iColor, char * pData )
 {
   int     iY;
   int     iX0;
   int     iX1;
   double  fA = (double) nX / 2.0;
   double  fB = (double) nY / 2.0;

   for ( iY = 0; iY < nY; iY++ )
   {
     iX0 = (int)( ( ( fB - sqrt( (double)iY * ( 2.0 * fB - (double)iY ) ) ) * fA ) / fB );
     iX1 = nX - iX0;
     memset( pData + iY * nPitch + iX0, iColor, iX1 - iX0 );
   }
 }

 void DimgFillMask( int nX, int nY, int nPitch, int iColor, char * pData )
 {
   int     iY;

   for ( iY = 0; iY < nY; iY++ )
   {
     memset( pData + iY * nPitch, iColor, nX );
   }
 }

 /*
  *----------------------------------------------------------------------
  *
  * DimgDispatchCommand --
  *
  * Tcl command connected with image name
  * Valid subcommands are:
  *      $img configure ?-option value?
  *
  * Results:
  *  None.
  *
  * Side effects:
  *  The image is deleted.
  *
  *----------------------------------------------------------------------
  */

 int DimgDispatchCommand( ClientData clientData, Tcl_Interp *pInterp, int nObj, Tcl_Obj *CONST aObj[] )
 {
   static char   *aszSubcommands[] = { "configure", "cget", "fill", "oval",  (char*)NULL };
   int            iSubcommand;
   DimgMaster    *pMaster = (DimgMaster *) clientData;
   Tcl_Obj       *pResult = Tcl_GetObjResult(pInterp);
   int            iResult = TCL_OK;

   if ( nObj == 1 )
   {
     Tcl_WrongNumArgs( pInterp, 1, aObj, "subcommand ?-option value?" );
     return TCL_ERROR;
   }

   if ( Tcl_GetIndexFromObj( pInterp, aObj[1], (const char **)aszSubcommands, "subcommand", 0, &iSubcommand ) != TCL_OK )
   {
     return TCL_ERROR;
   }

   switch( iSubcommand )
   {
   case 0: // configure
     if ( nObj % 2 != 0 )
     {
       Tcl_WrongNumArgs( pInterp, 2, aObj, "?-option value?" );
       return TCL_ERROR;
     }
     return DimgConfigureCmd( pInterp, pMaster, nObj-2, aObj+2 );
     break;

   case 1: // cget
     if ( nObj != 3 )
     {
       Tcl_WrongNumArgs( pInterp, 2, aObj, "-option" );
       return TCL_ERROR;
     }
     return DimgCgetCmd( pInterp, pMaster, nObj-2, aObj+2 );
     break;

   case 2: case 3:// fill, oval
     if ( nObj != 4 )
     {
       Tcl_WrongNumArgs( pInterp, 2, aObj, "[rgb|mask] value" );
       return TCL_ERROR;
     }
     return DimgFillOrOvalCmd( iSubcommand - 2, pInterp, pMaster, nObj-2, aObj+2 );
   }

   return iResult;
 }

 /*
  *----------------------------------------------------------------------
  *
  * DimgDeleteCommand --
  *
  *  This procedure is invoked when the image command for an image
  *  is deleted. It deletes the image.
  *
  * Results:
  *  None.
  *
  * Side effects:
  *  The image is deleted.
  *
  *----------------------------------------------------------------------
  */

 void DimgDeleteCommand( ClientData clientData )
 {
   DimgMaster *pMaster = (DimgMaster *) clientData;

 /*
  *   the process of deleting an image can begin from two points,
  *   either by deleting the image directly or by deleting the image
  *   command. however each process must do both. therefore we
  *   must check if the image master is in the process of being deleted
  *   before trying to delete it again
  */
   if ( pMaster->tkMaster )
   {
     Tk_DeleteImage( pMaster->pInterp, Tk_NameOfImage( pMaster->tkMaster) );
   }
 }

 int DimgCreateMaster
 (
     Tcl_Interp *pInterp,
     char *szName,
     int nObj,
     Tcl_Obj *CONST aObj[],
     Tk_ImageType *typePtr,
     Tk_ImageMaster pTkMaster,
     ClientData *pClientData
 )
 {
   DimgMaster   *pMaster;
   int           iResult;
   Tcl_Obj      *pResult = Tcl_GetObjResult(pInterp);

   /*

  • Allocate and initialize the Dimg image master record.
    */
   pMaster = (DimgMaster *) Tcl_Alloc(sizeof(DimgMaster));

   if( pMaster == NULL )
   {
     Tcl_AppendStringsToObj( pResult, "create dimg ", szName, ": Out of memory\n", NULL );
     return TCL_ERROR;
   }

   strcpy( pMaster->szName, szName );
   pMaster->pInstance = NULL;
   pMaster->tkMaster = pTkMaster;
   pMaster->pInterp = pInterp;
   pMaster->nInstance = 0;
   pMaster->nWidth = 100;
   pMaster->nHeight = 100;
   pMaster->nMaskPitch = 100;
   pMaster->dwMaskMode = 0;
   pMaster->dwImageMode = SRCCOPY;
   pMaster->bmImage = NULL;
   pMaster->bmMask = NULL;
   pMaster->pImageData = NULL;
   pMaster->pMaskData = NULL;
   memcpy( pMaster->aColorTable, g_aDefaultColorTable, sizeof(g_aDefaultColorTable) );

   *pClientData = (ClientData) pMaster;

   /*

  • Process configuration options given in the image create command.
    */
   if ( nObj > 1 )
   {
     iResult = DimgConfigureCmd( pInterp, pMaster, nObj, aObj );
     if ( iResult != TCL_OK )
     {
       return iResult;
     }
   }

   Tcl_CreateObjCommand( pInterp, szName, DimgDispatchCommand, (ClientData) pMaster, DimgDeleteCommand );
   Tk_ImageChanged( pTkMaster, 0, 0, pMaster->nWidth, pMaster->nHeight, pMaster->nWidth, pMaster->nHeight );

   return TCL_OK;
 }

 /*
  *----------------------------------------------------------------------
  *
  * DimgDisplay --
  *
  *  This procedure is invoked to draw a Dimg image.
  *
  * Results:
  *  None.
  *
  * Side effects:
  *  A portion of the image gets rendered in a pixmap or window.
  *
  *----------------------------------------------------------------------
  */

 void DimgDisplay
 (
   ClientData   clientData,
   Display     *pDisplay,
   Drawable     stDrawable,
   int          iImageX,
   int          iImageY,
   int          nWidth,
   int          nHeight,
   int          iDrawableX,
   int          iDrawableY
 )
 {
   HDC             hdcDest;
   HDC             hdcSource;
   TkWinDCState    stWinDCState;
   DimgInstance   *pInstance = (DimgInstance*) clientData;

   hdcDest = TkWinGetDrawableDC( pDisplay, stDrawable, &stWinDCState);
   hdcSource = CreateCompatibleDC( hdcDest );

   if ( pInstance->pMaster->dwMaskMode )
   {
     SelectObject( hdcSource, pInstance->pMaster->bmMask );
     SetDIBColorTable( hdcSource, 0, 8, pInstance->pMaster->aColorTable );
     BitBlt( hdcDest, iDrawableX, iDrawableY, nWidth, nHeight, hdcSource, iImageX, iImageY, pInstance->pMaster->dwMaskMode );
   }

   if ( pInstance->pMaster->dwImageMode )
   {
     SelectObject( hdcSource, pInstance->pMaster->bmImage );
     BitBlt( hdcDest, iDrawableX, iDrawableY, nWidth, nHeight, hdcSource, iImageX, iImageY, pInstance->pMaster->dwImageMode );
   }

   DeleteDC( hdcSource );
   TkWinReleaseDrawableDC( stDrawable, hdcDest, &stWinDCState );
 }

 /*
  *----------------------------------------------------------------------
  *
  * DimgGetInstance --
  *
  *  This procedure is called for each use of a Dimg image in a
  *  widget.
  *
  * Results:
  *  The return value is a token for the instance, which is passed
  *  back to us in calls to DimgDisplay and DimgDeleteInstance.
  *
  * Side effects:
  *  A data structure is set up for the instance (or, an existing
  *  instance is re-used for the new one).
  *
  *----------------------------------------------------------------------
  */

 ClientData DimgGetInstance( Tk_Window tkwin, ClientData clientData )
 {
   DimgInstance  *pInstance;
   DimgMaster    *pMaster = (DimgMaster *) clientData;
   Tcl_Obj       *pResult = Tcl_GetObjResult( pMaster->pInterp );

   /* Make a new instance of the image. */
   pInstance = (DimgInstance *) Tcl_Alloc( sizeof(DimgInstance) );

   if( !pInstance )
   {
     Tcl_AppendStringsToObj( pResult, "DimgGetInstance: Out of memory\n", NULL) ;
     return NULL;
   }

   pInstance->tkwin     = tkwin;
   pInstance->pMaster   = pMaster;
   pInstance->pNext     = pMaster->pInstance;
   pMaster->nInstance++;
   pMaster->pInstance = pInstance;

   return (ClientData) pInstance;
 }

 /*
  *----------------------------------------------------------------------
  *
  * DimgFreeInstance --
  *
  *  This procedure is called when a widget ceases to use a
  *  particular instance of an image.
  *
  * Results:
  *  None.
  *
  * Side effects:
  *  Internal data structures get cleaned up.
  *
  *----------------------------------------------------------------------
  */

 void DimgFreeInstance( ClientData clientData, Display * display )
 {

   DimgInstance  *pInstance = (DimgInstance*) clientData;
   DimgInstance  *pPrevInstance;

   pInstance->pMaster->nInstance--;

   if ( pInstance->pMaster->pInstance == pInstance )
   {
     pInstance->pMaster->pInstance = pInstance->pNext;
   }
   else
   {
     pPrevInstance = pInstance->pMaster->pInstance;
     while ( ( pPrevInstance->pNext != pInstance ) && pPrevInstance->pNext )
     {
       pPrevInstance = pPrevInstance->pNext;
     }
     if ( pPrevInstance->pNext )
     {
       pPrevInstance->pNext = pInstance->pNext;
     }
     else
     {
       //oops instance not found
     }
   }
   memset( pInstance, 0, sizeof(DimgInstance) );
   Tcl_Free( (char*) pInstance );

   return;
 }

 /*
  *----------------------------------------------------------------------
  *
  * DimgDeleteMaster --
  *
  *  This procedure is called by the image code to delete the
  *  master structure for an image.
  *
  * Results:
  *  None.
  *
  * Side effects:
  *  Resources associated with the image get freed.
  *
  *----------------------------------------------------------------------
  */

 void DimgDeleteMaster( ClientData clientData )
 {
   const char  *szName;
   DimgMaster  *pMaster = (DimgMaster *) clientData;

   if ( pMaster->tkMaster )
   {
     szName = Tk_NameOfImage( pMaster->tkMaster );
     pMaster->tkMaster = NULL;      // flag image delete in progress
     Tcl_DeleteCommand( pMaster->pInterp, szName );
   }

   if ( pMaster->bmImage )
   {
     DeleteObject( pMaster->bmImage );
     pMaster->bmImage = NULL;
   }
   if ( pMaster->bmMask )
   {
     DeleteObject( pMaster->bmMask );
     pMaster->bmMask = NULL;
   }

   Tcl_Free( (char *) pMaster );

   return;
 }

 void vRegisterDimg()
 {
   g_stDimgDesc.name =         "dimg";
   g_stDimgDesc.createProc =   DimgCreateMaster;
   g_stDimgDesc.getProc =      DimgGetInstance;
   g_stDimgDesc.displayProc =  DimgDisplay;
   g_stDimgDesc.freeProc =     DimgFreeInstance;
   g_stDimgDesc.deleteProc =   DimgDeleteMaster;
   Tk_CreateImageType( &g_stDimgDesc );
 }