Sunday, April 17, 2011

Иконки в Windows, куда же нам без них.

Приветствую.

Очень часто встречается вопрос: "Как получить иконку файла?". Конечно же, все просто! Windows API, а если не получается, значит ты плохо читал документацию, там ведь все прозрачно и поймет любой.

ExtractIconExtractAssociatedIconExtractIconEx и многие другие, не думаю, что стоит продолжать.

И так, все ли так просто как кажется?

Предисловие
Чтобы все это дело заработало, потребуется скомбинировать чуть ли не все подходы в одну высокоуровневую штуковину, которая бы получала путь к фалу и выдавала нам Bitmap, грубо говоря. С путем к файлу в принципе ясно, как же быть с Bitmap? Для легкости в использовании, в свое время, мне потребовалось связать HBITMAP + BITMAPINFO + Scan0 + HDC + Gdiplus::Bitmap. Что из этого вышло можно глянуть тут (DIB.h/DIB.cpp), приводить код-обвертку я не буду. Сразу оговорю, писал давненько, так что используется MFC, но если очень захотеть, то избавиться от него просто, т.к. используются только CString, CRect и т.п.

Приступим
Нам понадобится научить наш модуль распознавать входящий файл, будь то картинка, иконка, исполняемый файл или что-то другое. Зачем? Ну если рассудить логически, будь то картинка, иконка ее была бы самой уменьшенной картинкой. Будь это иконка, аналогично картинке, но вытянуть придется сначала HICON, а потом преобразовать в наш класс-обвертку CDIB. С исполняемым файлом практически, что и с иконкой. Пробегаемся по ресурсам файла в поиске иконки, если не удача, пробуем получить иконку как в общем случае. В большинстве своих случаев, этого вполне достаточно, но все же, нет ничего идеального.

И так, в итоге мы должны иметь возможность:
  1. Получить размер иконки по пути файла
  2. Получить иконку (HICON) по пути файла
  3. Получить наш класс-обвертку CDIB из иконки (HICON)
  4. Получить наш класс-обвертку CDIB по пути файла
  5. Получить иконку (HICON) из модуля (HMODULE) с указанным именем ресурса
  6. Получить иконку (HICON) из модуля (путь файла) с указанным именем ресурса
  7. Как дополнение получить системную иконку по идентификатору, подобно MessageBox
Вот такой вот небольшой список. Как вы могли догадаться речь пойдет о С++, поэтому приступим к icons.h:
#ifndef ICONS_H
#define ICONS_H

#include 
#include 
#include 
#include "DIB.h"

namespace Icons
{
 typedef struct {
  unsigned char bWidth;
  unsigned char bHeight;
  unsigned char bColorCount;
  unsigned char bReserved;
  short wPlanes;
  short wBitCount;
  unsigned int dwBytesInRes;
  unsigned int dwImageOffset;
 } ICONDIRENTRY;

 typedef struct {
  short idReserved; // = 0 ?
  short idType; // = 1 ?
  short idCount;
 } ICONDIR;

 int GetIconSize(CString fileName);
 HICON GetIcon(CString fileName);
 void GetDIBFromIcon(HICON icon, CDIB *dib);
 bool GetIcon(CString fileName, CDIB *dib);
 HICON GetIcon(HMODULE hModule, CString resName);
 HICON GetIcon(CString fileName, CString resName);
 HICON GetSystemIcon(UINT uType);
}

#endif /* ICONS_H */
Ну чтобы долго не раздумывать и не разжевывать, давайте сразу перейдем к непосредственной реализации выше описанного icons.cpp:
#include "icons.h"

using namespace Gdiplus;

int Icons::GetIconSize(CString fileName)
{
 int iconSize = GetSystemMetrics(SM_CXICON);
 FILE *f = NULL;
 _wfopen_s(&f, fileName.GetBuffer(), L"r+b");
 if(f)
 {
  ICONDIR iconDir = {0};
  fread(&iconDir, 1, sizeof(ICONDIR), f);
  if((iconDir.idReserved == 0) && (iconDir.idType == 1))
  {
   void *iconEntry = malloc(iconDir.idCount * sizeof(ICONDIRENTRY));
   fread(iconEntry, sizeof(ICONDIRENTRY), iconDir.idCount, f); 
   ICONDIRENTRY *p = (ICONDIRENTRY*)iconEntry;
   for(int i = 0; i < iconDir.idCount; i++, p++)
   {
    if(iconSize < p->bWidth)
    {
     iconSize = p->bWidth;
    }
   }
   free(iconEntry);
  }
  fclose(f);
 }
 return iconSize;
}

typedef struct {
 CString name;
 int index;
 int counter;
} ENUMDATA;

BOOL WINAPI EnumResNameProc(HMODULE, LPCTSTR, LPTSTR lpszName, LONG_PTR lParam)
{
 ENUMDATA *data = (ENUMDATA*)lParam;
 if(data->counter == data->index)
 {
  if(IS_INTRESOURCE(lpszName))
  {
   data->name.Format(L"#%d", (ULONG_PTR)lpszName);
  }
  else
  {
   data->name = lpszName;
  }
  return FALSE;
 }
 else
 {
  data->counter++;
  return TRUE;
 }
}

bool AnalizeIcon256(HICON icon)
{
 CDIB dib;
 Icons::GetDIBFromIcon(icon, &dib);
 if(!dib.Ready())
 {
  return false;
 }
 DIB_ARGB *p = (DIB_ARGB*)((int)dib.scan0 + (48 * dib.Width() + 48) * 4);
 int size = (dib.Width() - 48) * (dib.Height() - 48);
 for(int i = 0; i < size; i++, p++)
 {
  if(p->c != 0)
  {
   return true;
  }
 }
 return false;
}

HICON Icons::GetIcon(CString fileName)
{
 if(fileName.IsEmpty())
 {
  return NULL;
 }

 HICON icon = NULL;
 SHFILEINFO sfi = {0};
 LPITEMIDLIST pidl = SHSimpleIDListFromPath(fileName.GetBuffer());

 if(pidl && (fileName.Mid(0, 3) == L"::{"))
 {
  SHGetFileInfo((LPCWSTR)pidl, 0, &sfi, sizeof(SHFILEINFO), SHGFI_ICONLOCATION | SHGFI_PIDL);
  if(wcscmp(sfi.szDisplayName, L"\0") != 0)
  {
   fileName = sfi.szDisplayName;
  }
 }

 CString ext;
 int i = fileName.ReverseFind(L'.');
 if(i >= 0)
 {
  ext = fileName.Mid(i).MakeLower();
 }
 if((ext == L".exe") || (ext == L".ocx") || (ext == L".dll") || 
  (ext == L".scr") || (ext == L".bin") || (ext == L".cpl"))
 {
  HMODULE hModule = LoadLibraryEx(fileName.GetBuffer(), 0, LOAD_LIBRARY_AS_DATAFILE);
  if(hModule)
  {
   ENUMDATA enumData;
   if(sfi.iIcon >= 0)
   {
    enumData.index = sfi.iIcon;
    enumData.counter = 0;
    EnumResourceNames(hModule, MAKEINTRESOURCE(3 + DIFFERENCE), EnumResNameProc, (LONG_PTR)&enumData);
   }
   else
   {
    enumData.name.Format(L"#%d", -sfi.iIcon);
   }
   icon = GetIcon(hModule, enumData.name);
   FreeLibrary(hModule);
  }
 }
 else
 if((ext == L".ico") || (ext == L".icon"))
 {
  int iconSize = GetIconSize(fileName);
  icon = (HICON)LoadImage(0, fileName.GetBuffer(), IMAGE_ICON, iconSize, iconSize, LR_LOADFROMFILE | LR_COLOR);
 }

 if(!icon)
 {
  IImageList* imageList;
  OSVERSIONINFO osinf = {0};
  osinf.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
  GetVersionEx(&osinf);
  
  if((osinf.dwMajorVersion >= 6) && SUCCEEDED(SHGetImageList(SHIL_JUMBO, IID_IImageList, (void**)&imageList)))
  {
   SHGetFileInfo(fileName.GetBuffer(), 0, &sfi, sizeof(sfi), SHGFI_SYSICONINDEX); 
   imageList->GetIcon(sfi.iIcon, ILD_IMAGE | ILD_TRANSPARENT, &icon);
   if(!AnalizeIcon256(icon))
   {
    DestroyIcon(icon);
    icon = NULL;
   }
  }
  if(!icon && SUCCEEDED(SHGetImageList(SHIL_EXTRALARGE, IID_IImageList, (void**)&imageList)))
  {
   SHGetFileInfo(fileName.GetBuffer(), 0, &sfi, sizeof(sfi), SHGFI_SYSICONINDEX); 
   imageList->GetIcon(sfi.iIcon, ILD_IMAGE | ILD_TRANSPARENT, &icon);
  }
 }

 if(!icon && pidl && SHGetFileInfo((LPCWSTR)pidl, 0, &sfi, sizeof(SHFILEINFO), 
  SHGFI_ICON | SHGFI_LARGEICON | SHGFI_PIDL))
 {
  icon = sfi.hIcon;
 }

 if(pidl)
 {
  ILFree(pidl);
 }

 return icon;
}

HICON Icons::GetIcon(HMODULE hModule, CString resName)
{
 HICON icon = NULL;
 HRSRC hResource = FindResource(hModule, resName.GetBuffer(), MAKEINTRESOURCE(3 + DIFFERENCE));
 if(hResource)
 {
  HGLOBAL hMem = LoadResource(hModule, hResource);
  LPVOID lpResource = LockResource(hMem);

  if(hMem && lpResource)
  {
   int nId = LookupIconIdFromDirectoryEx((PBYTE)lpResource, TRUE, 0x200, 0x200, LR_DEFAULTCOLOR);
   hResource = FindResource(hModule, MAKEINTRESOURCE(nId), MAKEINTRESOURCE(3));
   if(hResource)
   {
    hMem = LoadResource(hModule, hResource);
    lpResource = LockResource(hMem);
    if(hMem && lpResource)
    {
     icon = CreateIconFromResourceEx((PBYTE)lpResource, SizeofResource(hModule, hResource),
      TRUE, 0x00030000, 0, 0, LR_DEFAULTCOLOR);
    }
   }
  }
 }
 return icon;
}

HICON Icons::GetIcon(CString fileName, CString resName)
{
 if(fileName.IsEmpty() || resName.IsEmpty())
 {
  return NULL;
 }
 HICON icon = NULL;
 HMODULE hModule = (HMODULE)LoadLibraryEx(fileName.GetBuffer(), NULL, LOAD_LIBRARY_AS_DATAFILE);
 if(hModule)
 {
  icon = GetIcon(hModule, resName);
  FreeLibrary(hModule);
 }
 return icon;
}

void Icons::GetDIBFromIcon(HICON icon, CDIB *dib)
{
 if(!icon || !dib)
 {
  return;
 }
 ICONINFO ii;
 GetIconInfo(icon, &ii);

 BITMAP bitmap;
 GetObject(ii.hbmColor, sizeof(BITMAP), &bitmap);

 HDC mainDC = CreateCompatibleDC(0);
 HDC maskDC = CreateCompatibleDC(0);
 HGDIOBJ mainOld = SelectObject(mainDC, ii.hbmColor);
 HGDIOBJ maskOld = SelectObject(maskDC, ii.hbmMask);

 dib->Resize(bitmap.bmWidth, bitmap.bmHeight);

 if(bitmap.bmBitsPixel < 32)
 {
  BitBlt(dib->dc, 0, 0, bitmap.bmWidth, bitmap.bmHeight, maskDC, 0, 0, SRCCOPY);
  BitBlt(dib->dc, 0, 0, bitmap.bmWidth, bitmap.bmHeight, maskDC, 0, 0, DSTINVERT);
  BitBlt(dib->dc, 0, 0, bitmap.bmWidth, bitmap.bmHeight, mainDC, 0, 0, SRCAND);
 }
 else
 {
  BitBlt(dib->dc, 0, 0, bitmap.bmWidth, bitmap.bmHeight, mainDC, 0, 0, SRCCOPY);
 }

 dib->ReflectVertical();

 // Check old icons >> 32 bits
 bool check = true;
 DIB_ARGB *p = dib->scan0;
 for(int i = 0; i < dib->Width() * dib->Height(); i++, p++)
 {
  if(p->a)
  {
   check = false;
   break;
  }
 }
 if(check)
 {
  p = dib->scan0;
  for(int i = 0; i < dib->Width() * dib->Height(); i++, p++)
  {
   if((p->r != 0x0) && (p->g != 0x0) && (p->b != 0x0))
   {
    p->a = 255;
   }
  }
 }
 SelectObject(maskDC, maskOld);
 SelectObject(mainDC, mainOld);
 DeleteDC(maskDC);
 DeleteDC(mainDC);
 DeleteObject(ii.hbmMask);
 DeleteObject(ii.hbmColor); 
}

bool Icons::GetIcon(CString fileName, CDIB *dib)
{
 if(!fileName.IsEmpty())
 {
  HICON icon = GetIcon(fileName);
  if(icon)
  {
   GetDIBFromIcon(icon, dib);
   DestroyIcon(icon);
   return true;
  }
 }
 return false;
}

HICON Icons::GetSystemIcon(UINT uType)
{
 CString resName;
 if((uType & MB_ICONEXCLAMATION) == MB_ICONEXCLAMATION)
 {
  resName = L"#101";
 }
 else
 if((uType & MB_ICONQUESTION) == MB_ICONQUESTION)
 {
  resName = L"#102";
 }
 else
 if((uType & MB_ICONERROR) == MB_ICONERROR)
 {
  resName = L"#103";
 }
 else
 if((uType & MB_ICONINFORMATION) == MB_ICONINFORMATION)
 {
  resName = L"#104";
 }
 else
 {
  return NULL;
 }
 return GetIcon(L"user32.dll", resName);
}
Как так вот. Надеюсь не очень страшно, и разжевывать дословно не стоит. Если какие то вопросы, прошу, спрашивайте не стесняйтесь.

3 comments:

  1. А у проводника с его ShellAPI нет разве доступного интерфейса, которым сам проводник пользуется? Наверняка есть, и наверняка оно через него чуть проще.

    ReplyDelete
  2. Да тут как раз и ShellAPI и используется. Проводник, я думаю, как то так же мучается.

    ReplyDelete
  3. Он мучается по-разному, к нему можно плагины писать, которые будут помогать ему мучаться для различных типов файлов, у него разные картинки для разных режимов - не говоря о встроенных в иконки изображений разного размера, можно вспомнить режим "Эскизы страниц", где он глубже в файлы лезет. И всем этим можно тоже пользоваться из своих программ, если подходы найти.

    А иконку обычную достать из файла так, как это делает проводник - можно одним вызовом. Вот как это у меня на Delphi написано:

    if FileName <> '' then
    begin

    // Получаем системную иконку для файла в тек. строке
    SHGetFileInfo(PChar(FileName),
    FILE_ATTRIBUTE_NORMAL,
    SFI,
    0,
    SHGFI_USEFILEATTRIBUTES or SHGFI_ICON or SHGFI_SMALLICON);

    Icon.Picture.Icon.Handle := SFI.hIcon;
    ACanvas.Draw(ARect.Left, ARect.Top, Icon.Picture.Graphic);
    ADone := True;
    end;

    ReplyDelete