Version 2 of Retrieve file icon using the Win32 API

Updated 2007-03-09 13:22:44

MJ - To get the icon windows explorer displays for a file requires quite a lot of registry searching to determine the source of the icon (dll, exe, ico) and the index in that file. Even then it is not guaranteed that the correct icon will be returned, because Windows explorer seems to use several mechanisms to determine the correct icon.

The Win32 API function SHGetFileInfo [L1 ] will reliably retrieve the correct icon from a file (note that any file is supported, not just exe, dll and ico files).

The C extension below provides a new Tcl command [::shellicon::get ?options? fileName] which will create a Tk image from the icon associated to fileName. The options are flags that modify the appearance of the icon:

 -large get the large version of the icon (small is the default)
 -selected get the appearance of the icon when it is selected
 -open get the icon that is shown when the file is opened (e.g. an opened folder)

Example

 # dll provides the package shellicon
 load shellicon0.1.dll

 label .l1 -image [::shellicon::get c:/Windows]
 label .l2 -image [::shellicon::get -large  c:/Windows]
 label .l3 -image [::shellicon::get -selected c:/Windows]
 label .l4 -image [::shellicon::get -large -selected c:/Windows]
 label .l5 -image [::shellicon::get -open c:/Windows]
 label .l6 -image [::shellicon::get -large -open c:/Windows]
 label .l7 -image [::shellicon::get -open -selected c:/Windows]
 label .l8 -image [::shellicon::get -large -open -selected c:/Windows]

 grid .l1 .l2 .l3 .l4 .l5 .l6 .l7 .l8

 catch {console show}

Build

To build with gcc use:

 gcc shellicon.c -lgdi32 -ltclstub85 -ltkstub85 -I/c/Tcl/include -L/c/Tcl/lib\
 -DUSE_TCL_STUBS -DUSE_TK_STUBS -shared -o shellicon0.1.dll

Source

 #include <tcl.h>
 #include <tk.h>
 #include <windows.h>
 #include <shellapi.h>
 #include <stdio.h>

 static int GetIcon_Cmd(ClientData cdata, Tcl_Interp *interp, int objc,  Tcl_Obj * CONST objv[]) {
   SHFILEINFO shfi;
   ICONINFO iconInfo ;
   BITMAP bmp;
   long imageSize ;
   char * bitBuffer , * byteBuffer ;
   int i, index;
   int result, hasAlpha;
   const char * image_name;
   Tk_PhotoHandle photo;
   Tk_PhotoImageBlock block;
   const char * file_name;
   Tcl_DString ds;
   HDC hdc;
   int bitSize;
   unsigned int uFlags;


   static CONST char *options[] = {
     "-large", "-open", "-selected", NULL};
   enum IOption {
     ILARGE, IOPEN, ISELECTED
   };

   if (objc < 2) {
     Tcl_WrongNumArgs(interp,1,objv,"?options? fileName");
     return TCL_ERROR;
   }

   /* SHGFI_ICON == SHGFI_LARGEICON so large is the default, select small instead
  • then remove the flag if -large is specified
    */

   uFlags = SHGFI_ICON | SHGFI_SMALLICON;

   for (i=1 ; i < objc-1 ; i++) {
     result = Tcl_GetIndexFromObj(interp, objv[i], options, "option", 0,
                                  (int *) &index);
     if (result != TCL_OK) {
       return result;
     }
     switch (index) {
     case ILARGE:
       /* setting LARGE is equivalent to unsetting SMALL */
       uFlags ^= SHGFI_SMALLICON;
       break;
     case IOPEN:
       uFlags |= SHGFI_OPENICON;
       break;
     case ISELECTED:
       uFlags |= SHGFI_SELECTED;
       break;
     default:
       Tcl_Panic("option lookup failed");
     }
   }

   /* Normalize the filename */
   file_name = Tcl_GetString (objv[objc-1]);
   file_name = Tcl_TranslateFileName(interp, file_name, &ds);
   if (file_name == NULL) {
     return TCL_ERROR;
   }

   result = SHGetFileInfo(
                          Tcl_DStringValue(&ds),
                          0,
                          &shfi,
                          sizeof(SHFILEINFO),
                          uFlags
                          );
   Tcl_DStringFree(&ds);

   if (result == 0) {
     Tcl_AppendResult(interp, "failed to load icon",NULL);
     return TCL_ERROR;
   }

   GetIconInfo(shfi.hIcon, &iconInfo);

   result = GetObject(
                      iconInfo.hbmMask,
                      sizeof(BITMAP),
                      (void *)&bmp
                      );


   bitSize = bmp.bmWidth * bmp.bmHeight * bmp.bmBitsPixel / 8;

   bitBuffer = ckalloc(bitSize);
   GetBitmapBits(iconInfo.hbmMask,bitSize,bitBuffer);

   result = GetObject(
                      iconInfo.hbmColor,
                      sizeof(BITMAP),
                      (void *)&bmp
                      );

   imageSize = bmp.bmWidth * bmp.bmHeight * bmp.bmBitsPixel / 8;
   byteBuffer = ckalloc(imageSize);
   GetBitmapBits(iconInfo.hbmColor,imageSize,byteBuffer);

   /* Do some mask and Alpha channel voodoo, because not all Icons define an alpha channel
  • and MS has decided to make completely transparent the default in that case, AAARGGH
  • Might be some bit masking I am missing here though.
    */

   hasAlpha = 0;
   for (i = 0 ; i < imageSize ; i+=4) {
     if (byteBuffer[i+offsetof(RGBQUAD,rgbReserved)]!=0) {
       hasAlpha = 1;
       break;
     }
   }


 #define BIT_SET(x,y) (((x) >> (8-(y)) ) & 1 )

   for (i=0;i<bitSize;i++) {
     if (hasAlpha) break;
     // if (i%2==0) {fprintf(stderr,"\n");}
     int bit = 0;
     for (bit=0; bit < 8 ; bit++) {
       if (BIT_SET(bitBuffer[i],bit)) {
         // fprintf(stderr,"0");
         byteBuffer[(i*8+bit)*4+3] = 0;
       } else {
         // fprintf(stderr,"1");
         byteBuffer[(i*8+bit)*4+3] = 255;
       }
     }
   }

   /* setup the Tk block structure */
   block.pixelPtr = byteBuffer;
   block.width = bmp.bmWidth;
   block.height = bmp.bmHeight;
   block.pitch = bmp.bmWidthBytes;
   block.pixelSize = bmp.bmBitsPixel/8;
   block.offset[0]  = offsetof(RGBQUAD,rgbRed);
   block.offset[1]  = offsetof(RGBQUAD,rgbGreen);
   block.offset[2]  = offsetof(RGBQUAD,rgbBlue);
   block.offset[3] = offsetof(RGBQUAD,rgbReserved);

   /* Create the image */
   result = Tcl_Eval(interp,"image create photo");
   if (result != TCL_OK) {
     return TCL_ERROR;
   }
   image_name = Tcl_GetStringResult(interp);
   photo = Tk_FindPhoto(interp, image_name);

   result = Tk_PhotoPutBlock( interp,photo, &block ,0,0,block.width, block.height,TK_PHOTO_COMPOSITE_SET);

   if (result != TCL_OK) {
     return TCL_ERROR;
   }

   //cleanup
   ckfree(bitBuffer);
   ckfree(byteBuffer);

   DeleteObject(iconInfo.hbmMask);
   DeleteObject(iconInfo.hbmColor);
   DestroyIcon(shfi.hIcon);

   Tcl_AppendResult(interp, image_name, NULL);
   return TCL_OK;
 }

 int DLLEXPORT
 Shellicon_Init(Tcl_Interp *interp)
 {
   if (Tcl_InitStubs(interp, "8.4", 0) == 0L) {
     return TCL_ERROR;
   }
   if (Tk_InitStubs(interp, "8.4", 0) == 0L) {
     return TCL_ERROR;
   }
   Tcl_CreateObjCommand(interp, "shellicon::get", GetIcon_Cmd, NULL, NULL);
   Tcl_PkgProvide(interp, "shellicon", "0.1");
   return TCL_OK;
 }

Category Package