Hace ya dos años escribí un código para modificar los iconos de la sección de recursos de un ejecutable .exe o .dll. Diversas funciones los extraían o los cambiaban. También escribí código para asignar esos iconos desde un TIcon. Todo ese código lo publiqué en CD. El caso es que desde el último gran terremoto que ocurrió en el club vecino he empezado a temer que se pierda y he decidido traer el tema aquí, esta vez en forma de tutorial.
El código es todo C/C++ con API y algo de la VCL en lo que se refiere a la asignación de un TIcon.
Saludos.
PD: Añado código y ejemplos en delphi de como guardar un HICON en un archivo.ico y de como asignarlo a un objeto TIcon sin perder colores.
Estudio de Iconos en los recursos de los ejecutables y mas...
Started by
escafandra
, Dec 11 2009 05:11 PM
12 replies to this topic
#1
Posted 11 December 2009 - 05:11 PM
#2
Posted 11 December 2009 - 05:12 PM
En primer lugar, he de aclarar que los Iconos de le sección de recursos están agrupados en grupos con una cabecera de tipo GRPICONDIR. En esa cabecera tenemos, entre otras cosas, el número de imágenes que contiene y un array de entradas a cada imagen. Cada entrada es del tipo MEMICONDIRENTRY o GRPICONDIRENTRY (son lo mismo).
Los archivos de iconos se comportan igual pero únicamente contienen un sólo Grupo con una o varias imágenes. Su cabecera es del tipo ICONDIR y este contiene, entre otras cosas, el número de iconos y las entradas correspondientes a cada uno del tipo ICONDIRENTRY.
Las definiciones tienen su fuente aquí.
Por si el tema de las definiciones de las cabeceras no queda claro, publico cómo lo he usado. Archivo Iconos.h:
Dicho esto, lo primero que tenemos que conseguir es enumerar los recursos por su tipo, lo que nos interesa es el tipo RT_GROUP_ICON que son los grupos de iconos, es decir enumeraremos esos grupos sabiendo que cada uno puede tener varias imágenes de iconos de distinto tamaño, resolución y número de colores.
Estas funciones enumeran la sección de recursos con el propósito de contarlos con GetCountRes y encontrar un recurso determinado por su íncice, comenzando desde 0, con FindResource. Esta última función, a diferencia de la API FindResource, toma los índices como un int. La API FindResource toma los índices como una cadena formada por el carácter # seguida de un número que comienza por el 1. El primer recurso es el “#1”.
Por el momento doy por terminada esta parte.
Saludos.
Los archivos de iconos se comportan igual pero únicamente contienen un sólo Grupo con una o varias imágenes. Su cabecera es del tipo ICONDIR y este contiene, entre otras cosas, el número de iconos y las entradas correspondientes a cada uno del tipo ICONDIRENTRY.
Las definiciones tienen su fuente aquí.
Por si el tema de las definiciones de las cabeceras no queda claro, publico cómo lo he usado. Archivo Iconos.h:
cpp
#ifndef iconosH #define iconosH //--------------------------------------------------------------------------- // Tomado de [url]http://msdn.microsoft.com/en-us/library/ms997538.aspx[/url] // Modificado del original. // Necesario para alinear a Word el compilador #pragma pack( push ) #pragma pack( 2 ) // These first two structs represent how the icon information is stored // when it is bound into a EXE or DLL file. Structure members are WORD // aligned and the last member of the structure is the ID instead of // the imageoffset. typedef struct { BYTE bWidth; // Width of the image BYTE bHeight; // Height of the image (times 2) BYTE bColorCount; // Number of colors in image (0 if >=8bpp) BYTE bReserved; // Reserved WORD wPlanes; // Color Planes WORD wBitCount; // Bits per pixel DWORD dwBytesInRes; // how many bytes in this resource? WORD nID; // the ID } MEMICONDIRENTRY, *LPMEMICONDIRENTRY, GRPICONDIRENTRY, *LPGRPICONDIRENTRY; typedef struct { WORD idReserved; // Reserved WORD idType; // resource type (1 for icons) WORD idCount; // how many images? MEMICONDIRENTRY idEntries[1]; // the entries for each image } MEMICONDIR, *LPMEMICONDIR, GRPICONDIR, *LPGRPICONDIR; // These next two structs represent how the icon information is stored // in an ICO file. typedef struct { BYTE bWidth; // Width of the image BYTE bHeight; // Height of the image (times 2) BYTE bColorCount; // Number of colors in image (0 if >=8bpp) BYTE bReserved; // Reserved WORD wPlanes; // Color Planes WORD wBitCount; // Bits per pixel DWORD dwBytesInRes; // how many bytes in this resource? DWORD dwImageOffset; // where in the file is this image } ICONDIRENTRY, *LPICONDIRENTRY; typedef struct { WORD idReserved; // Reserved WORD idType; // resource type (1 for icons) WORD idCount; // how many images? ICONDIRENTRY idEntries[1]; // the entries for each image } ICONDIR, *LPICONDIR; #pragma pack( pop ) //--------------------------------------------------------------------------- #endif
Dicho esto, lo primero que tenemos que conseguir es enumerar los recursos por su tipo, lo que nos interesa es el tipo RT_GROUP_ICON que son los grupos de iconos, es decir enumeraremos esos grupos sabiendo que cada uno puede tener varias imágenes de iconos de distinto tamaño, resolución y número de colores.
cpp
//------------------------------------------------------------------------------ // Funciones de enumeración de resources //------------------------------------------------------------------------------ // Declaraciones previas // Definición del tipo de función que acepta EnumResourceNames typedef BOOL (__stdcall *TP)(); // Estructura para comunicar EnumResourceNames con la función de enumeración struct TResInGr{ int Count; int Id; char* ResName; HRSRC hRes; }; // Función de enumeración para la cuenta de elementos y encontrar un Resource BOOL CALLBACK EnumResGrProc(HANDLE hModule, LPCTSTR lpszType, LPTSTR lpszName, LONG lParam) { TResInGr *Res = (TResInGr*)lParam; if(Res->Count == Res->Id){ Res->ResName = lpszName; Res->hRes = ::FindResource(hModule, lpszName, lpszType); } Res->Count++; return true; } // Devuelve el Nº de Elementos de lpszType int GetCountRes(HANDLE hModule, LPCTSTR lpszType) { TResInGr Res = {0}; Res.Id = -1; EnumResourceNames(hModule, lpszType, (TP)EnumResGrProc, LPARAM(&Res)); return Res.Count; } // Devuelve el Nº de Elementos de lpszType int GetCountRes(char *FuenteExe, LPCTSTR lpszType) { HANDLE hModule = LoadLibrary(FuenteExe); int Count = GetCountRes(hModule, lpszType); FreeLibrary(hModule); return Count; } // Devuelve un HRSRC con Indice Id del array de resources lpszType // Id: Indice del Grupo de iconos como si fuese una array comenzando por 0 HRSRC FindResource(HMODULE hModule, int Id, LPCTSTR lpszType) { TResInGr Res = {0}; Res.Id = Id; EnumResourceNames(hModule, lpszType, (TP)EnumResGrProc, LPARAM(&Res)); return Res.hRes; }
Estas funciones enumeran la sección de recursos con el propósito de contarlos con GetCountRes y encontrar un recurso determinado por su íncice, comenzando desde 0, con FindResource. Esta última función, a diferencia de la API FindResource, toma los índices como un int. La API FindResource toma los índices como una cadena formada por el carácter # seguida de un número que comienza por el 1. El primer recurso es el “#1”.
Por el momento doy por terminada esta parte.
Saludos.
#3
Posted 11 December 2009 - 05:14 PM
Explicados los conceptos preliminares y la enumeración de recursos, pasamos a la extracción de los recursos. He escrito dos funciones bastante similares.
La primera, ExtractResIconFromModule, extrae un grupo de imágenes de la sección de recursos de un ejecutable o dll. La extracción es un buffer que contiene el binario del resource. La segunda, GetFileMemIconFromModule, hace lo propio pero en un buffer imagen de un archivo.ico. De cada uno se estos buffer se podrá extraer, entonces, una imagen de un icono individual, pero esto lo realizarán otras funciones.
ExtractResIconFromModule, extrae un grupo de imágenes en un buffer de memoria con cabecera GRPICONDIR (o MEMICONDIR) con las entradas a cada imagen y estas. Este buffer puede usarse para pasar el recurso entero tipo RT_GROUP_ICON a una dll o ejecutable. Esto lo realizará otra función, AddIconToExe.
Y para ilustrar el uso de esta función voy a colocar el código de AddIconToExe, que añade el recurso extraído con ExtractResIconFromModule, en un ejecutable o una dll:
Y para terminar de ilustrar estas funciones, propongo esta otra que hace uso de las dos:
En el siguiente post publicaré el modo de guardar en un archivo, un Grupo de Iconos con las funciones GetFileMemIconFromModule y SaveResIconToFile.
Saludos.
La primera, ExtractResIconFromModule, extrae un grupo de imágenes de la sección de recursos de un ejecutable o dll. La extracción es un buffer que contiene el binario del resource. La segunda, GetFileMemIconFromModule, hace lo propio pero en un buffer imagen de un archivo.ico. De cada uno se estos buffer se podrá extraer, entonces, una imagen de un icono individual, pero esto lo realizarán otras funciones.
ExtractResIconFromModule, extrae un grupo de imágenes en un buffer de memoria con cabecera GRPICONDIR (o MEMICONDIR) con las entradas a cada imagen y estas. Este buffer puede usarse para pasar el recurso entero tipo RT_GROUP_ICON a una dll o ejecutable. Esto lo realizará otra función, AddIconToExe.
cpp
//--------------------------------------------------------------------------- // Devuelve un ResMem Icon de un hModule // Devuelve el tamaño de la memoria de archivo de un Icono de un hModule // Retorna 0 si falla, 1 si Buffer==0 y AllSize si devuelve el valor en Buffer. // Precisa de un Buffer previo de tamaño Size // Si Buffer = NULL, devuelve en Size el tamaño necesario para el buffer // Id: Indice del Grupo de iconos como si fuese una array comenzando por 0 int ExtractResIconFromModule(HMODULE hModule, int Id, void* Buffer, int* Size) { LPGRPICONDIR grpIconDir; LPGRPICONDIR grpSrcIconDir; LPGRPICONDIRENTRY grpIconEntry; LPICONIMAGE grpIconImage; LPICONIMAGE resIconImage; BYTE* IconMem = (BYTE*)Buffer; HRSRC hRes; // handle para res. info. del ejecutable int IconsCount = 0; int AllSize = sizeof(GRPICONDIR); // Cuento cuantos iconos tiene el recurso y la memoria necesaria hRes = FindResource(hModule, Id, RT_GROUP_ICON); if(hRes){ grpSrcIconDir = (LPGRPICONDIR)LoadResource(hModule, hRes); IconsCount = grpSrcIconDir->idCount; for(int n=0; n<IconsCount; n++) AllSize += sizeof(ICONDIRENTRY) + grpSrcIconDir->idEntries[n].dwBytesInRes; } if(IconsCount==0){ *Size = 0; return 0; } if(Buffer == 0 ){ *Size = AllSize; return 1; } if(*Size < AllSize) return 0; // Preparo la Cabecera General grpIconDir grpIconDir = (LPGRPICONDIR)IconMem; grpIconDir->idReserved = 0; grpIconDir->idType = 1; grpIconDir->idCount = IconsCount; grpIconEntry = LPMEMICONDIRENTRY(IconMem + sizeof(GRPICONDIR) - sizeof(GRPICONDIRENTRY)); // Localizar el ICON resource en el ejecutable y sus Imagenes. hRes = NULL; grpIconImage = (LPICONIMAGE)((BYTE*)grpIconDir + sizeof(GRPICONDIR) + (sizeof(GRPICONDIRENTRY)*(grpIconDir->idCount-1))); for(int n=0; n<IconsCount; n++){ int nID = grpSrcIconDir->idEntries[n].nID; hRes = ::FindResource(hModule, MAKEINTRESOURCE(nID), RT_ICON); // Preparo las cabeceras Entrada de cada Imagen resIconImage = (ICONIMAGE*)LoadResource(hModule, hRes); grpIconEntry[n].bWidth = resIconImage->icHeader.biWidth; grpIconEntry[n].bHeight = resIconImage->icHeader.biHeight/2; grpIconEntry[n].bColorCount = NColors(resIconImage->icHeader.biBitCount); grpIconEntry[n].bReserved = 0; grpIconEntry[n].wPlanes = 1; grpIconEntry[n].wBitCount = resIconImage->icHeader.biBitCount; grpIconEntry[n].dwBytesInRes = SizeofResource(hModule, hRes); grpIconEntry[n].nID = n; // Copio la imagen memcpy(grpIconImage, resIconImage, grpIconEntry[n].dwBytesInRes); // grpIconImage = (LPICONIMAGE)((BYTE*)grpIconImage + grpIconDir->idEntries[n-1].dwBytesInRes); grpIconImage = (LPICONIMAGE)((BYTE*)grpIconImage + grpIconEntry[n].dwBytesInRes); } return AllSize; }
Y para ilustrar el uso de esta función voy a colocar el código de AddIconToExe, que añade el recurso extraído con ExtractResIconFromModule, en un ejecutable o una dll:
cpp
//--------------------------------------------------------------------------- // grpIconDir : Buffer con el recurso del tipo RT_GROUP_ICON extraido con ExtractResIconFromModule // DestinoExe: Nombre de un ejecutable o dll // ResName: Nombre que se le dará al recurso añadico al ejecutable o dll // BorraIconResPrevio: Si es trae se borraran los recursos previos RT_GROUP_ICON del ejecutable o dll bool AddIconToExe(LPGRPICONDIR grpIconDir, char *DestinoExe, char* ResName, bool BorraIconResPrevio) { LPGRPICONDIRENTRY IconEntry; LPICONIMAGE IconImage; LPBYTE lpResLock; bool Result = true; if(!grpIconDir) return false; if(!DestinoExe || !*DestinoExe) return false; if(!ResName || !*ResName) return false; // Crea espacio para las cabeceras del icono recurso int HeaderSize = sizeof(GRPICONDIR)+(sizeof(GRPICONDIRENTRY)*(grpIconDir->idCount-1)); HANDLE hUpdateRes = BeginUpdateResource(DestinoExe, BorraIconResPrevio); // Localizo la primera imagen del icono a pasar al .exe IconImage = (LPICONIMAGE)((BYTE*)grpIconDir + sizeof(GRPICONDIR) + (sizeof(GRPICONDIRENTRY)*(grpIconDir->idCount-1))); for(int n=0; n<grpIconDir->idCount; n++){ lpResLock = (BYTE*)LockResource(IconImage); // Abrir el fichero donde añadir el icono. if (hUpdateRes != NULL){ // Actualizar el resource destino Result &= UpdateResource(hUpdateRes, RT_ICON, MAKEINTRESOURCE(grpIconDir->idEntries[n].nID), MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), lpResLock, grpIconDir->idEntries[n].dwBytesInRes); } // Localizo la siguiente Imagen IconImage = (LPICONIMAGE)((BYTE*)IconImage + grpIconDir->idEntries[n].dwBytesInRes); } // Y la grabamos como "indice" o cabecera de grupo de iconos Result &= UpdateResource(hUpdateRes, RT_GROUP_ICON, ResName, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), grpIconDir, HeaderSize); // Escribir los cambios y cerrar. Result &= EndUpdateResource(hUpdateRes, FALSE); return Result; }
Y para terminar de ilustrar estas funciones, propongo esta otra que hace uso de las dos:
cpp
//------------------------------------------------------------------------------ // Pasa todas las imágenes del primer Grupo Icono (principal por defecto del ejecutable) // de un exe (o dll) a otro bool AddIconToExe(char *FuenteExe, char *DestinoExe, char* ResName, bool BorraIconResPrevio) { bool Result; BYTE* Buffer; int Size; LPGRPICONDIR grpIconDir; HMODULE hModule; hModule = LoadLibrary(FuenteExe); // Calculo el tamaño necesario para el Buffer ExtractResIconFromModule (hModule, 0, 0, &Size); Buffer = new BYTE[Size]; // Extraigo del ejecutable (hModule) el primer grupo de iconos que obtengo en el Buffer ExtractResIconFromModule(hModule, 0, Buffer, &Size); grpIconDir = (LPGRPICONDIR)Buffer; FreeLibrary(hModule); if(grpIconDir) // Si el Buffer es válido Result = AddIconToExe(grpIconDir, DestinoExe, ResName, BorraIconResPrevio); else // Si el buffer no es válido Result = false; // Libero la memoria del Grupo de iconos antes de retornar. delete [] Buffer; return Result; }
En el siguiente post publicaré el modo de guardar en un archivo, un Grupo de Iconos con las funciones GetFileMemIconFromModule y SaveResIconToFile.
Saludos.
#4
Posted 11 December 2009 - 05:15 PM
Continúo con la extracción de iconos de la sección de recursos de un ejecutable.
GetFileMemIconFromModule está diseñada para extraer un grupo de imágenes del recurso en un buffer que pueda ser escrito directamente a un archivo.ico. El formato es un buffer con una cabecera ICONDIR, sus entradas a las imágenes y éstas mismas. Un archivo de este tipo, a diferencia con la sección de recursos de un ejecutable, sólo tiene un grupo de iconos definido en su cabecera, aunque lo habitual es que, ese grupo sólo tenga una imagen, nada impide que tenga muchas más de diferente resolución y número de colores.
Como puede observarse, al igual que la función ExtractResIconFromModule, se recorren todas las imágenes del grupo. El código puede ser base para otro que obtenga uno o varios iconos en particular, según determinado criterio, y proporcionar un buffer sólo con esas imágenes. Para eso será necesario calcular previamente el tamaño del buffer y luego rellenarlo cuidadosamente.
Para ilustrar el uso de esta función propongo un código que guarda el grupo de iconos en un archivo de iconos.ico, SaveResIconToFile:
Como comentaba mas arriba, pueden crearse mas funciones para escojer iconos concretos y to todo el grupo, las bases están puestas en esta serie de post.
Saludos.
GetFileMemIconFromModule está diseñada para extraer un grupo de imágenes del recurso en un buffer que pueda ser escrito directamente a un archivo.ico. El formato es un buffer con una cabecera ICONDIR, sus entradas a las imágenes y éstas mismas. Un archivo de este tipo, a diferencia con la sección de recursos de un ejecutable, sólo tiene un grupo de iconos definido en su cabecera, aunque lo habitual es que, ese grupo sólo tenga una imagen, nada impide que tenga muchas más de diferente resolución y número de colores.
cpp
//------------------------------------------------------------------------------ // Devuelve en Buffer Una imagen de archivo.ico para poder guardar en disco. // Devuelve el tamaño de la memoria de archivo de un Icono de un hModule // Retorna 0 si falla, 1 si Buffer==0 y AllSize si devuelve el valor en Buffer. // Si Buffer = NULL, devuelve en Size el tamaño necesario para el buffer // Id: Indice del Grupo de iconos como si fuese una array comenzando por 0 int GetFileMemIconFromModule(HMODULE hModule, int Id, void* Buffer, int *Size) { LPICONDIR IconDir; LPICONDIRENTRY IconEntry; LPICONIMAGE resIconImage; LPGRPICONDIR grpIconDir; HRSRC hRes; // handle para res. info. del ejecutable int IconsCount = 0; int AllSize = sizeof(ICONDIR)-sizeof(ICONDIRENTRY); DWORD ImageOffset; // Cuento cuantos iconos tiene el recurso y la memoria necesaria hRes = FindResource(hModule, Id, RT_GROUP_ICON); if(hRes){ grpIconDir = (LPGRPICONDIR)LoadResource(hModule, hRes); IconsCount = grpIconDir->idCount; for(int n=0; n<IconsCount; n++) AllSize += sizeof(ICONDIRENTRY) + grpIconDir->idEntries[n].dwBytesInRes; } if(IconsCount==0){ *Size = 0; return 0; } if(Buffer == 0 ){ *Size = AllSize; return 1; } if(*Size < AllSize) return 0; // Preparo la Cabecera General grpIconDir setmem(Buffer, 0, AllSize); IconDir = (LPICONDIR)Buffer; IconDir->idReserved = 0; IconDir->idType = 1; IconDir->idCount = IconsCount; // IconEntry apunta a la entrada del primer icono IconDir->idEntries[0] // IconEntry = LPICONDIRENTRY(Buffer + sizeof(ICONDIR) - sizeof(ICONDIRENTRY)); IconEntry = &IconDir->idEntries[0]; // Localizar el ICON resource en el ejecutable y sus Imagenes. hRes = NULL; ImageOffset = sizeof(ICONDIR) + (sizeof(ICONDIRENTRY)*(IconDir->idCount-1)); // Recorro las imágenes del recurso y preparo las entradas y las imágenes en el Buffer for(int n=0; n<IconsCount; n++){ int nID = grpIconDir->idEntries[n].nID; hRes = ::FindResource(hModule, MAKEINTRESOURCE(nID), RT_ICON); resIconImage = (ICONIMAGE*)LoadResource(hModule, hRes); IconEntry[n].bWidth = resIconImage->icHeader.biWidth; IconEntry[n].bHeight = resIconImage->icHeader.biHeight/2; IconEntry[n].bColorCount = NColors(resIconImage->icHeader.biBitCount); IconEntry[n].bReserved = 0; IconEntry[n].wPlanes = 1; IconEntry[n].wBitCount = resIconImage->icHeader.biBitCount; IconEntry[n].dwBytesInRes = SizeofResource(hModule, hRes); IconEntry[n].dwImageOffset = ImageOffset; // Copio la imagen memcpy((BYTE*)Buffer+ImageOffset, resIconImage, IconEntry[n].dwBytesInRes); ImageOffset += IconEntry[n].dwBytesInRes; } return AllSize; }
Como puede observarse, al igual que la función ExtractResIconFromModule, se recorren todas las imágenes del grupo. El código puede ser base para otro que obtenga uno o varios iconos en particular, según determinado criterio, y proporcionar un buffer sólo con esas imágenes. Para eso será necesario calcular previamente el tamaño del buffer y luego rellenarlo cuidadosamente.
Para ilustrar el uso de esta función propongo un código que guarda el grupo de iconos en un archivo de iconos.ico, SaveResIconToFile:
cpp
//------------------------------------------------------------------------------ // Guarda en disco File.ico el grupo de Icono de un exe o dll // Id: Indice del Grupo de iconos como si fuese una array comenzando por 0 bool SaveResIconToFile(char *FuenteExe, char* DestFileName, int Id) { BYTE* Buffer; int Size; HMODULE hModule; hModule = LoadLibrary(FuenteExe); // Calculo el tamaño necesario para el Buffer GetFileMemIconFromModule(hModule, Id, 0, &Size); Buffer = new BYTE[Size]; // Extraigo del ejecutable (hModule) el grupo de iconos que obtengo en el Buffer // Este Buffer es una imagen en memoria de un archivo.ico que se puede guardar directamente. int S = GetFileMemIconFromModule(hModule, Id, Buffer, &Size); if(S==0) return false; // Creo y guardo el archivo de iconos HANDLE hFile = CreateFile(DestFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); unsigned long dwBytesWritten; if (INVALID_HANDLE_VALUE != hFile){ WriteFile(hFile, Buffer, Size, &dwBytesWritten, NULL); CloseHandle(hFile); } FreeLibrary(hModule); delete [] Buffer; return (dwBytesWritten == Size); }
Como comentaba mas arriba, pueden crearse mas funciones para escojer iconos concretos y to todo el grupo, las bases están puestas en esta serie de post.
Saludos.
#5
Posted 11 December 2009 - 05:16 PM
Ahora vamos a cambiar el icono de un ejecutable, para ello escribimos la función CambiaIcono, que se encargará de cambiar el Icono de un ejecutable tomándolo de un archivo.ico
El código añade un icono al ejecutable que queramos, o puede sustituir los iconos del mismo por el nuestro.
Otra forma de hacerlo puede ser mapeando en memoria un HICON utilizando las mismas estructuras y las API GetIconInfo y GetDIBits. Claro que si es engorroso utilizar GetDIBits, siempre se puede echar mano de las VCL:
La desventaja de este cómodo método es que sólo es satisfactorio para iconos de 16 colores, si tiene mas se reducen, al menos en BCB 5 y 6. Me imagino que en delphi 5 y 6 pasará lo mismo. Creo que las versiones de 2008 no tienen este problema.
Saludos.
cpp
bool CambiaIcono(char *FuenteICO, char *DestinoExe, char* ResName, bool BorraIconResPrevio=false) { HANDLE hFile; LPBYTE lpBuffer; LPBYTE lpResLock; bool Result = true; if(!FuenteICO || !*FuenteICO) return false; if(!DestinoExe || !*DestinoExe) return false; if(!ResName || !*ResName) return false; // Abrimos el icono.ico en modo de lectura binaria DWORD dwFileSize, dwBytesRead; hFile = CreateFile(FuenteICO, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE != hFile){ dwFileSize = GetFileSize(hFile, NULL); lpBuffer = new BYTE[dwFileSize]; ReadFile(hFile, lpBuffer, dwFileSize, &dwBytesRead, NULL); CloseHandle(hFile); } // Convierto el Buffer a formato Icono con cabeceras... ICONDIR *IconDir = (ICONDIR*)lpBuffer; // Crea espacio para las cabeceras del icono recurso int HeaderSize = sizeof(GRPICONDIR)+(sizeof(GRPICONDIRENTRY)*(IconDir->idCount-1)); BYTE *HeaderIconRec = new BYTE[HeaderSize]; GRPICONDIR *grpIconDir = (GRPICONDIR*)HeaderIconRec; // Copio la cabecera común con IconDir memcpy(grpIconDir, IconDir, sizeof(GRPICONDIR)-sizeof(GRPICONDIRENTRY)); // Abrimos el recurso del exe HANDLE hUpdateRes = BeginUpdateResource(DestinoExe, BorraIconResPrevio); for(int n=0; n<grpIconDir->idCount; n++){ // Copio las cabeceras memcpy(&grpIconDir->idEntries[n], &IconDir->idEntries[n], sizeof(GRPICONDIRENTRY)); grpIconDir->idEntries[n].nID = n + 100; // Localizo la imagen del icono a pasar al .exe BYTE *IconImage = (BYTE*)IconDir + IconDir->idEntries[n].dwImageOffset; lpResLock = (BYTE*)LockResource(IconImage); // Abrir el fichero donde añadir el icono. if (hUpdateRes != NULL){ // Actualizar el resource destino Result &= UpdateResource(hUpdateRes, RT_ICON, MAKEINTRESOURCE(grpIconDir->idEntries[n].nID), MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), lpResLock, IconDir->idEntries[n].dwBytesInRes); } } // Y la grabamos comp "indice" o cabecera de grupo de iconos Result &= UpdateResource(hUpdateRes, RT_GROUP_ICON, ResName, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), grpIconDir, HeaderSize); // Escribir los cambios y cerrar. EndUpdateResource(hUpdateRes, FALSE); delete lpBuffer; delete HeaderIconRec; return Result; }
El código añade un icono al ejecutable que queramos, o puede sustituir los iconos del mismo por el nuestro.
Otra forma de hacerlo puede ser mapeando en memoria un HICON utilizando las mismas estructuras y las API GetIconInfo y GetDIBits. Claro que si es engorroso utilizar GetDIBits, siempre se puede echar mano de las VCL:
cpp
TIcon Icon = new TIcon; Icon->Handle = hIcon; // el HICON a convertir TMemoryStream *Stream = new TMemoryStream; Icon->SaveToStream(Stream); // Stream->Memory sería el buffer a usar // Convierto el Buffer a formato Icono con cabeceras... ICONDIR *IconDir = (ICONDIR*)Stream->Memory;
La desventaja de este cómodo método es que sólo es satisfactorio para iconos de 16 colores, si tiene mas se reducen, al menos en BCB 5 y 6. Me imagino que en delphi 5 y 6 pasará lo mismo. Creo que las versiones de 2008 no tienen este problema.
Saludos.
#6
Posted 11 December 2009 - 05:22 PM
En el post anterior comentaba la forma de conseguir incluir en un ejecutable o dll un nuevo icono como un recurso. Comentaba también ciertas limitaciones del Builder y el delphi, en ediciones algo antiguas, para el manejo de los colores de un icono pasado por su Handle HICON. Hice referencia a la posibilidad de mapear el HICON en memoria.
Aquí dejo un ejemplo de como pasar un HICON a memoria, en este caso un bloque con el formato del archivo del icono.ico que puede ser volcado al disco como un .ico.
Nos sirve también para crear un recurso en un ejecutable o dll. Para esto tendremos que pasar, como en el post anterior, un puntero al ICONIMAGE para así poder crear el recurso.
El archivo de definiciones es el mismo que en el post anterior.
Para ilustrar el uso voy a colocar dos ejemplos prácticos. En el primero guardo un hIcon en un archivo y lo vuelvo a abrir para ver lo que sucede:
Ahora vamos a pasar la Imagen de un hIcon a un TIcon. Para ello utilizamos un TMemoryStream como paso intermedio. Creo que su uso es bastante intuitivo:
Con esta serie intensiva dedicada a los iconos de la sección de recursos de los ejecutables, espero haber contribuido a esclarecer algunos asuntos un tanto oscuros de Windows, o al menos a aportar código que pudiera ser útil a alguien en un momento dado.
Saludos.
Aquí dejo un ejemplo de como pasar un HICON a memoria, en este caso un bloque con el formato del archivo del icono.ico que puede ser volcado al disco como un .ico.
Nos sirve también para crear un recurso en un ejecutable o dll. Para esto tendremos que pasar, como en el post anterior, un puntero al ICONIMAGE para así poder crear el recurso.
El archivo de definiciones es el mismo que en el post anterior.
cpp
# include "Iconos.h" //--------------------------------------------------------------------------- long NColors(WORD bitCount) { if (bitCount == 1 || bitCount == 4 || bitCount == 8) return 1 << bitCount; else if (bitCount >= 24) return 0; return -1; } //--------------------------------------------------------------------------- // Devuelve una imagen de archivo.ico para poder guardar en disco.... // Reserva la memoria necesaria devolviendo en Size el Tamaño // Requiere liberar luego la memoria con VirtualFree(Mem, 0, MEM_RELEASE); void* hIconToMem(HICON hIcon, int BitCountPerPixel, int* Size) { // Localizo la información del icono y su tamaño en un fichero.ico HDC hDC = ::GetDC(NULL); // ScreenDC ICONINFO IconInfo; BITMAP bmpAND={0}; BITMAP bmpXOR={0}; bool I = GetIconInfo(hIcon, &IconInfo); GetObject(IconInfo.hbmMask, sizeof(BITMAP), &bmpAND); GetObject(IconInfo.hbmColor, sizeof(BITMAP), &bmpXOR); if(BitCountPerPixel==0) BitCountPerPixel=bmpXOR.bmPlanes*bmpXOR.bmBitsPixel; int RawDataSizeAND=((((bmpAND.bmWidth*bmpAND.bmBitsPixel)+31) & ~31) >> 3)*bmpAND.bmHeight; int RawDataSizeXOR=((((bmpXOR.bmWidth*BitCountPerPixel)+31) & ~31) >> 3)*bmpXOR.bmHeight; int RawDataSize = RawDataSizeAND + RawDataSizeXOR; int PalSize=(BitCountPerPixel>8 ? 0 :1 << BitCountPerPixel)<<2; // RGBQUAD 4 int AllSize= sizeof(ICONDIR)+sizeof(BITMAPINFOHEADER)+PalSize+RawDataSizeAND+RawDataSizeXOR; int Width = bmpAND.bmWidth; int Height = bmpAND.bmHeight; // Reservo memoria para el fichero BYTE* FileIconMem = (BYTE*)VirtualAlloc(0, AllSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); ICONDIR* IconDir = (ICONDIR*)FileIconMem; // Obtengo los Bits de cada parte Mask y Color BITMAPINFO bmiAND; BITMAPINFO *bmiICON = (BITMAPINFO*)(FileIconMem + sizeof(ICONDIR)); LPVOID lpBitsXOR = (BITMAPINFO*)(FileIconMem + sizeof(ICONDIR) + sizeof(BITMAPINFOHEADER) + PalSize); LPVOID lpBitsAND = (BITMAPINFO*)((BYTE*)lpBitsXOR + RawDataSizeXOR); // Preparo la cabecera del Icon IconDir->idReserved = 0; IconDir->idType = 1; IconDir->idCount = 1; IconDir->idEntries[0].bWidth = Width; IconDir->idEntries[0].bHeight = Height; IconDir->idEntries[0].bColorCount = NColors(BitCountPerPixel); IconDir->idEntries[0].bReserved = 0; IconDir->idEntries[0].wPlanes = 0; // Color Planes IconDir->idEntries[0].wBitCount = 0;//BitCountPerPixel; // Bits per pixel IconDir->idEntries[0].dwBytesInRes = AllSize-sizeof(ICONDIR); // How many bytes in this resource? IconDir->idEntries[0].dwImageOffset = sizeof(ICONDIR); // Where in the file is this image? LPICONIMAGE IconImage = (LPICONIMAGE)(bmiICON); memset(IconImage, 0, sizeof(BITMAPINFOHEADER)); IconImage->icHeader.biSize = sizeof(BITMAPINFOHEADER); IconImage->icHeader.biWidth = Width; IconImage->icHeader.biHeight = Height; IconImage->icHeader.biPlanes = 1; IconImage->icHeader.biBitCount = BitCountPerPixel; IconImage->icHeader.biSizeImage = RawDataSize; // Preparo BITMAPINFOHEADER para la Mascara (bmiAND) memcpy(&bmiAND, bmiICON, sizeof(BITMAPINFOHEADER)); bmiAND.bmiHeader.biSizeImage = RawDataSizeAND; bmiAND.bmiHeader.biBitCount = 1; bmiAND.bmiHeader.biClrUsed = 1; bmiAND.bmiHeader.biClrImportant = 1; // Recupero los bits de cada hBitmap del icono GetDIBits(hDC, IconInfo.hbmColor, 0, Height, lpBitsXOR, bmiICON, DIB_RGB_COLORS); GetDIBits(hDC, IconInfo.hbmMask, 0, Height, lpBitsAND, &bmiAND, DIB_RGB_COLORS); // Sumo las dos alturas de ambos bitmaps del icono IconImage->icHeader.biHeight = Height*2; ReleaseDC(0, hDC); // Salida *Size = AllSize; return FileIconMem; }
Para ilustrar el uso voy a colocar dos ejemplos prácticos. En el primero guardo un hIcon en un archivo y lo vuelvo a abrir para ver lo que sucede:
cpp
TIcon *Icon = new TIcon; Icon->LoadFromFile("MI_ICONO.ICO"); // Convertimos el HICON a imagen en memoria del .ico int Size; void* FileIconMem = hIconToMem(Icon->Handle, 32, &Size); HANDLE hFile=CreateFile("FilePath.ico", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); DWORD dwWritten; WriteFile(hFile, FileIconMem, Size, &dwWritten, 0); CloseHandle(hFile); VirtualFree(FileIconMem, 0, MEM_RELEASE); // Lo recuperamos para ver que ha pasado Icon->LoadFromFile("FilePath.ico"); Image1->Picture->Icon->Assign(Icon);
Ahora vamos a pasar la Imagen de un hIcon a un TIcon. Para ello utilizamos un TMemoryStream como paso intermedio. Creo que su uso es bastante intuitivo:
cpp
// Creo un MemoryStream TMemoryStream* Stream = new TMemoryStream; // Leo el mapa del hIcon deseado int Size; void* FileIconMem = hIconToMem(hIcon, 32, &Size); // lo paso al MemoryStream Stream->Write(FileIconMem, Size); Stream->Position = 0; // Libero el mapa VirtualFree(FileIconMem, 0, MEM_RELEASE); // Creo el icono con la imagen del hIcon TIcon *Icon = new TIcon; Stream->Position = 0; // Para situar le puntero de lectura al principio del Stream Icon->LoadFromStream(Stream); // Cargo la imagen del Stream
Con esta serie intensiva dedicada a los iconos de la sección de recursos de los ejecutables, espero haber contribuido a esclarecer algunos asuntos un tanto oscuros de Windows, o al menos a aportar código que pudiera ser útil a alguien en un momento dado.
Saludos.
#7
Posted 11 December 2009 - 05:36 PM
wow, amigo te envío una reverencia, no mejor 2, no mejor 3...mmm...todas las que sea necesarias , excelente tuto amigo .
#8
Posted 11 December 2009 - 07:52 PM
Saludos.
Excelente tutorial, solo que el código esta en C/C++ pero la lógica esta bien.
Gracias de todos modos por el aporte.
Excelente tutorial, solo que el código esta en C/C++ pero la lógica esta bien.
Gracias de todos modos por el aporte.
#9
Posted 12 December 2009 - 02:24 PM
Muchas gracias a vosotros por la acogida tan generosa que brindáis a este tutorial.
Me vais a permitir que añada algo mas:
La función
Puede resultar muy útil para el manejo de iconos de mas de 16 colores cuando tratamos de asimilar a un TIcon el Handle extraido con las funciones dadas o con las APIs, como ExtractAssociatedIcon, que devuelven un manejador HICON. La función descrita reserva memoria con VirtualAlloc y requiere liberarla después de la llamada como podeis ver en los ejemplos publicados. Voy a proponer otra implementación con una técnica que se usa mucho en algunas API Win32. Se trata de realizar primero una consulta a la función, para saber el tamaño del buffer necesario para, una vez reservado, volverla a llamar de forma definitiva:
Ilustro la función creando un TIcon a partir de un Handle:
Cada desarrollador encontrará uno u otro sistema mas cómodo o mas adecuado. Las ideas están sobre la mesa. Gracias por vuestro interés.
Saludos.
Me vais a permitir que añada algo mas:
La función
cpp
void* hIconToMem(HICON hIcon, int BitCountPerPixel, int* Size);
Puede resultar muy útil para el manejo de iconos de mas de 16 colores cuando tratamos de asimilar a un TIcon el Handle extraido con las funciones dadas o con las APIs, como ExtractAssociatedIcon, que devuelven un manejador HICON. La función descrita reserva memoria con VirtualAlloc y requiere liberarla después de la llamada como podeis ver en los ejemplos publicados. Voy a proponer otra implementación con una técnica que se usa mucho en algunas API Win32. Se trata de realizar primero una consulta a la función, para saber el tamaño del buffer necesario para, una vez reservado, volverla a llamar de forma definitiva:
cpp
//--------------------------------------------------------------------------- // Devuelve en Buffer Una imagen de archivo.ico para poder guardar en disco. // Devuelve el tamaño de la memoria de archivo de un Icono // Retorna 0 si falla, 1 si Buffer==0 y AllSize si devuelbe el valor en Buffer. // Si Buffer = NULL, devuelve en Size el tamaño necesario para el buffer int hIconToMem(HICON hIcon, int BitCountPerPixel, void* Buffer, int* Size) { // Localizo la información del icono y su tamaño en un fichero.ico HDC hDC = ::GetDC(NULL); // ScreenDC ICONINFO IconInfo; BITMAP bmpAND={0}; BITMAP bmpXOR={0}; BYTE* FileIconMem = (BYTE*)Buffer; bool I = GetIconInfo(hIcon, &IconInfo); GetObject(IconInfo.hbmMask, sizeof(BITMAP), &bmpAND); GetObject(IconInfo.hbmColor, sizeof(BITMAP), &bmpXOR); if(BitCountPerPixel==0) BitCountPerPixel=bmpXOR.bmPlanes*bmpXOR.bmBitsPixel; int RawDataSizeAND=((((bmpAND.bmWidth*bmpAND.bmBitsPixel)+31) & ~31) >> 3)*bmpAND.bmHeight; int RawDataSizeXOR=((((bmpXOR.bmWidth*BitCountPerPixel)+31) & ~31) >> 3)*bmpXOR.bmHeight; int RawDataSize = RawDataSizeAND + RawDataSizeXOR; int PalSize=(BitCountPerPixel>8 ? 0 :1 << BitCountPerPixel)<<2; // RGBQUAD 4 int AllSize= sizeof(ICONDIR)+sizeof(BITMAPINFOHEADER)+PalSize+RawDataSizeAND+RawDataSizeXOR; int Width = bmpAND.bmWidth; int Height = bmpAND.bmHeight; // Si Buffer es nulo, termino retornando el tamaño necesario del buffer en Bytes if(Buffer == 0){ *Size = AllSize; return 1; } if(*Size < AllSize) return 0; // Convierto Buffer a ICONDIR ICONDIR* IconDir = (ICONDIR*)Buffer; // Obtengo los Bits de cada parte Mask y Color BITMAPINFO bmiAND; BITMAPINFO *bmiICON = (BITMAPINFO*)(FileIconMem + sizeof(ICONDIR)); LPVOID lpBitsXOR = (BITMAPINFO*)(FileIconMem + sizeof(ICONDIR) + sizeof(BITMAPINFOHEADER) + PalSize); LPVOID lpBitsAND = (BITMAPINFO*)((BYTE*)lpBitsXOR + RawDataSizeXOR); // Preparo la cabecera del Icon IconDir->idReserved = 0; IconDir->idType = 1; IconDir->idCount = 1; IconDir->idEntries[0].bWidth = Width; IconDir->idEntries[0].bHeight = Height; IconDir->idEntries[0].bColorCount = NColors(BitCountPerPixel); IconDir->idEntries[0].bReserved = 0; IconDir->idEntries[0].wPlanes = 0; // Color Planes IconDir->idEntries[0].wBitCount = 0;//BitCountPerPixel; // Bits per pixel IconDir->idEntries[0].dwBytesInRes = AllSize-sizeof(ICONDIR); // How many bytes in this resource? IconDir->idEntries[0].dwImageOffset = sizeof(ICONDIR); // Where in the file is this image? LPICONIMAGE IconImage = (LPICONIMAGE)(bmiICON); memset(IconImage, 0, sizeof(BITMAPINFOHEADER)); IconImage->icHeader.biSize = sizeof(BITMAPINFOHEADER); IconImage->icHeader.biWidth = Width; IconImage->icHeader.biHeight = Height; IconImage->icHeader.biPlanes = 1; IconImage->icHeader.biBitCount = BitCountPerPixel; IconImage->icHeader.biSizeImage = RawDataSize; // Preparo BITMAPINFOHEADER para la Mascara (bmiAND) memcpy(&bmiAND, bmiICON, sizeof(BITMAPINFOHEADER)); bmiAND.bmiHeader.biSizeImage = RawDataSizeAND; bmiAND.bmiHeader.biBitCount = 1; bmiAND.bmiHeader.biClrUsed = 1; bmiAND.bmiHeader.biClrImportant = 1; // Recupero los bits de cada hBitmap del icono GetDIBits(hDC, IconInfo.hbmColor, 0, Height, lpBitsXOR, bmiICON, DIB_RGB_COLORS); GetDIBits(hDC, IconInfo.hbmMask, 0, Height, lpBitsAND, &bmiAND, DIB_RGB_COLORS); // Sumo las dos alturas de ambos bitmaps del icono IconImage->icHeader.biHeight = Height*2; ReleaseDC(0, hDC); // Salida return AllSize;; }
Ilustro la función creando un TIcon a partir de un Handle:
cpp
// Creo un MemoryStream TMemoryStream* Stream = new TMemoryStream; // Leo el mapa del hIcon deseado void* FileIconMem; int Size; if(hIconToMem(hIcon, 32, 0, &Size)){ // Reservo un buffer. FileIconMem = (void*)new char[Size]; }else return; hIconToMem(hIcon, 32, FileIconMem, &Size); // lo paso al MemoryStream Stream->Write(FileIconMem, Size); Stream->Position = 0; // Libero el mapa delete [] FileIconMem; // Creo el icono con la imagen del hIcon TIcon *Icon = new TIcon; Stream->Position = 0; // Para situar le puntero de lectura al principio del Stream Icon->LoadFromStream(Stream); // Cargo la imagen del Stream en el TIcon
Cada desarrollador encontrará uno u otro sistema mas cómodo o mas adecuado. Las ideas están sobre la mesa. Gracias por vuestro interés.
Saludos.
#10
Posted 12 December 2009 - 08:26 PM
¡Excelente material!
Me quedo chiquito de tan semejante muestra de conocimiento.
Muchas gracias escafandra por compartirlo.
Saludos,
Me quedo chiquito de tan semejante muestra de conocimiento.
Muchas gracias escafandra por compartirlo.
Saludos,
#11
Posted 04 January 2010 - 05:14 AM
Me he dado cuenta de cierta demanda para poder escribir un archivo.ico a partir de un HICON. Esa misma demanda se refiere a convertir un HICON en un TIcon. Claro que lo mas fácil es hacer TIcon.Handle:= hIcon, pero como ya expliqué esto sólo funciona correctamente en iconos de 16 colores o menos. Muy posiblemente el error de colores esté corregido en las nuevas versiones de delphi, pero no en la 7 que está muy extendida. Parece ser que este tema no está resuelto para muchos usuarios tanto de delphi como de otros lenguajes. El tutorial presentado en este hilo está escrito en C/C++ pero es perfectamente trasladable a delphi. El presente mensaje viene a mostrar, con código y ejemplos, como resolver el problema en delphi.
Primeramente debemos definir las estructuras necesarias para trabajar con iconos a bajo nivel desde la API:
Seguidamente vamos a implementar la función que permite pasar de un HICON a una imagen en memoria del correspondiente archivo.ico. Como hice en C/C++, voy a proponer dos funciones casi idénticas. La primera devuelve un puntero a la imagen solicitada y el programador será responsable de liberar dicha memoria. La segunda implementación permite encontrar el tamaño necesitado para que el programador localice él mismo la memoria necesaria. Con una segunda llamada recibirá la imagen deseada.
Ambas funciones reciben como parámetros principales el Handle del Icono y los Bits por pixel de la imagen, que se relaciona directamente con el número de colores con los que queremos representar la imagen. Windows utilizará en el HICON tantos colores como sea capaz e representar en el modo gráfico con el que estemos trabajando, normalmente un valor de BitCountPerPixel de 32. Nosotros podremos reducir el valor si así lo consideramos necesario.
La primera función auxiliar, NColors, necesaria se dedica a conseguir el número de colores según el valor de BitCountPerPixel. Observar que para valores de 24 y 32 el resultado es cero:
Ahora la función hIconToMem que se encargará de conseguir una imagen en memoria de un fichero.ico a par´ir de un Handle HICON. El código está lo suficientemente comentado:
Seguidamente muestro una función similar, la diferencia está en que será en desarrollador el responsable de localizar y liberar la memoria necesaria, teniendo libertad en el modo de hacerlo. Para elle necesitaremos saber el tamaño necesario. Nuestra nueva función tiene dos usos, el primero informar del tamaño necesario para el buffer. Esto se consigue pasando un nulo como valor del buffer. El segundo uso es el verdadero propósito de la función, es decir llenar el bufer con la imagen en memoria del archivo.ico deseado.
Por no extenderme demasiado termino aquí y dejo para el siguiente mensaje los sencillos ejemplos de uso de estas funciones.
Saludos.
Primeramente debemos definir las estructuras necesarias para trabajar con iconos a bajo nivel desde la API:
delphi
// These next two structs represent how the icon information is stored // in an ICO file. {$ALIGN 1} //{$ALIGN ON} type TICONDIRENTRY = record bWidth: BYTE; // Width of the image bHeight: BYTE; // Height of the image (times 2) bColorCount: BYTE; // Number of colors in image (0 if >=8bpp) bReserved: BYTE; // Reserved wPlanes: WORD; // Color Planes wBitCount: WORD; // Bits per pixel dwBytesInRes: DWORD; // how many bytes in this resource? dwImageOffset: DWORD; // where in the file is this image end; PICONDIRENTRY = ^TICONDIRENTRY; type TICONDIR = record idReserved: WORD; // Reserved idType: WORD; // resource type (1 for icons) idCount: WORD; // how many images? idEntries: array[0..0] of TICONDIRENTRY; // the entries for each image end; PICONDIR = ^TICONDIR; type tagICONIMAGE= record icHeader: BITMAPINFOHEADER; // DIB header icColors: array[0..0] of RGBQUAD; // Color table icXORarray: array[0..0] of BYTE; // DIB bits for XOR mask icANDarray: array[0..0] of BYTE; // DIB bits for AND mask end; PICONIMAGE = ^tagICONIMAGE; {$ALIGN OFF}
Seguidamente vamos a implementar la función que permite pasar de un HICON a una imagen en memoria del correspondiente archivo.ico. Como hice en C/C++, voy a proponer dos funciones casi idénticas. La primera devuelve un puntero a la imagen solicitada y el programador será responsable de liberar dicha memoria. La segunda implementación permite encontrar el tamaño necesitado para que el programador localice él mismo la memoria necesaria. Con una segunda llamada recibirá la imagen deseada.
Ambas funciones reciben como parámetros principales el Handle del Icono y los Bits por pixel de la imagen, que se relaciona directamente con el número de colores con los que queremos representar la imagen. Windows utilizará en el HICON tantos colores como sea capaz e representar en el modo gráfico con el que estemos trabajando, normalmente un valor de BitCountPerPixel de 32. Nosotros podremos reducir el valor si así lo consideramos necesario.
La primera función auxiliar, NColors, necesaria se dedica a conseguir el número de colores según el valor de BitCountPerPixel. Observar que para valores de 24 y 32 el resultado es cero:
delphi
//--------------------------------------------------------------------------- // Convierte bitCount a número de colores function NColors(bitCount: WORD): integer; begin Result:= -1; if (bitCount = 1) or (bitCount = 4) or (bitCount = 8) then Result:= 1 shl bitCount else if (bitCount >= 24) then Result:= 0; end;
Ahora la función hIconToMem que se encargará de conseguir una imagen en memoria de un fichero.ico a par´ir de un Handle HICON. El código está lo suficientemente comentado:
delphi
//--------------------------------------------------------------------------- // Devuelve un puntero con la imagen de archivo.ico para poder guardar en disco. // Devuelve el tamaño de la memoria de archivo de un Icono en Size // Es necesario liberar el puntero devuelto tras usarlo: usar VirtualFree function hIconToMem(hIcon: Thandle; BitCountPerPixel: integer; var Size: integer): Pointer; var hDC: Thandle; IconInfo: TICONINFO; bmpAND, bmpXOR: BITMAP; FileIconMem: Pointer; RawDataSizeAND, RawDataSizeXOR, RawDataSize, PalSize, AllSize, Width, Height: integer; IconDir: PICONDIR; bmiAND: BITMAPINFO; bmiICON: PBITMAPINFO; lpBitsXOR, lpBitsAND: Pointer; IconImage: PICONIMAGE; begin // Localizo la información del icono y su tamaño en un fichero.ico hDC:= GetDC(0); // ScreenDC ZeroMemory(@bmpAND, sizeof(BITMAP)); ZeroMemory(@bmpXOR, sizeof(BITMAP)); GetIconInfo(hIcon, IconInfo); GetObject(IconInfo.hbmMask, sizeof(BITMAP), @bmpAND); GetObject(IconInfo.hbmColor, sizeof(BITMAP), @bmpXOR); if(BitCountPerPixel=0) then BitCountPerPixel:= bmpXOR.bmPlanes*bmpXOR.bmBitsPixel; RawDataSizeAND:= ((((bmpAND.bmWidth*bmpAND.bmBitsPixel)+31) and not 31) shr 3)*bmpAND.bmHeight; RawDataSizeXOR:= ((((bmpXOR.bmWidth*BitCountPerPixel)+31) and not 31) shr 3)*bmpXOR.bmHeight; RawDataSize:= RawDataSizeAND + RawDataSizeXOR; PalSize:=0; if(BitCountPerPixel<=8) then PalSize:= (1 shl BitCountPerPixel) shl 2; // RGBQUAD 4 AllSize:= sizeof(TICONDIR)+sizeof(BITMAPINFOHEADER)+PalSize+RawDataSizeAND+RawDataSizeXOR; Width := bmpAND.bmWidth; Height := bmpAND.bmHeight; // Reservo memoria para el fichero FileIconMem:= VirtualAlloc(nil, AllSize, MEM_COMMIT, PAGE_READWRITE); IconDir:= PICONDIR(FileIconMem); // Obtengo los Bits de cada parte Mask y Color bmiICON:= PBITMAPINFO(DWORD(FileIconMem) + sizeof(TICONDIR)); lpBitsXOR:= PBITMAPINFO(DWORD(FileIconMem) + sizeof(TICONDIR) + sizeof(BITMAPINFOHEADER) + PalSize); lpBitsAND:= PBITMAPINFO(DWORD(lpBitsXOR) + RawDataSizeXOR); // Preparo la cabecera del Icon IconDir.idReserved:= 0; IconDir.idType:= 1; IconDir.idCount:= 1; IconDir.idEntries[0].bWidth:= Width; IconDir.idEntries[0].bHeight:= Height; IconDir.idEntries[0].bColorCount:= NColors(BitCountPerPixel); IconDir.idEntries[0].bReserved:= 0; IconDir.idEntries[0].wPlanes:= 0; // Color Planes IconDir.idEntries[0].wBitCount:= 0;//BitCountPerPixel; // Bits per pixel IconDir.idEntries[0].dwBytesInRes:= AllSize-sizeof(TICONDIR); // How many bytes in this resource? IconDir.idEntries[0].dwImageOffset:= sizeof(TICONDIR); // Where in the file is this image? IconImage:= PICONIMAGE(bmiICON); ZeroMemory(IconImage, sizeof(BITMAPINFOHEADER)); IconImage.icHeader.biSize:= sizeof(BITMAPINFOHEADER); IconImage.icHeader.biWidth:= Width; IconImage.icHeader.biHeight:= Height; IconImage.icHeader.biPlanes:= 1; IconImage.icHeader.biBitCount:= BitCountPerPixel; IconImage.icHeader.biSizeImage:= RawDataSize; // Preparo BITMAPINFOHEADER para la Mascara (bmiAND) CopyMemory(@bmiAND, bmiICON, sizeof(BITMAPINFOHEADER)); bmiAND.bmiHeader.biSizeImage:= RawDataSizeAND; bmiAND.bmiHeader.biBitCount:= 1; bmiAND.bmiHeader.biClrUsed:= 1; bmiAND.bmiHeader.biClrImportant:= 1; // Recupero los bits de cada hBitmap del icono GetDIBits(hDC, IconInfo.hbmColor, 0, Height, lpBitsXOR, bmiICON^, DIB_RGB_COLORS); GetDIBits(hDC, IconInfo.hbmMask, 0, Height, lpBitsAND, bmiAND, DIB_RGB_COLORS); // Sumo las dos alturas de ambos bitmaps del icono IconImage.icHeader.biHeight:= Height*2; // Salida Size:= AllSize; Result:= FileIconMem; end;
Seguidamente muestro una función similar, la diferencia está en que será en desarrollador el responsable de localizar y liberar la memoria necesaria, teniendo libertad en el modo de hacerlo. Para elle necesitaremos saber el tamaño necesario. Nuestra nueva función tiene dos usos, el primero informar del tamaño necesario para el buffer. Esto se consigue pasando un nulo como valor del buffer. El segundo uso es el verdadero propósito de la función, es decir llenar el bufer con la imagen en memoria del archivo.ico deseado.
delphi
//--------------------------------------------------------------------------- // Devuelve en Buffer Una imagen de archivo.ico para poder guardar en disco. // Devuelve el tamaño de la memoria de archivo de un Icono. // Retorna 0 si falla, 1 si Buffer==0 y AllSize si devuelbe el valor en Buffer. // Si Buffer = NULL, devuelve en Size el tamaño necesario para el buffer function _hIconToMem(hIcon: Thandle; BitCountPerPixel: integer; Buffer: pointer; var Size: integer): integer; var hDC: Thandle; IconInfo: TICONINFO; bmpAND, bmpXOR: BITMAP; FileIconMem: PBYTE; RawDataSizeAND, RawDataSizeXOR, RawDataSize, PalSize, AllSize, Width, Height: integer; IconDir: PICONDIR; bmiAND: BITMAPINFO; bmiICON: PBITMAPINFO; lpBitsXOR, lpBitsAND: Pointer; IconImage: PICONIMAGE; begin // Localizo la información del icono y su tamaño en un fichero.ico hDC:= GetDC(0); // ScreenDC ZeroMemory(@bmpAND, sizeof(BITMAP)); ZeroMemory(@bmpXOR, sizeof(BITMAP)); FileIconMem:= PBYTE(Buffer); GetIconInfo(hIcon, IconInfo); GetObject(IconInfo.hbmMask, sizeof(BITMAP), @bmpAND); GetObject(IconInfo.hbmColor, sizeof(BITMAP), @bmpXOR); if(BitCountPerPixel=0) then BitCountPerPixel:= bmpXOR.bmPlanes*bmpXOR.bmBitsPixel; RawDataSizeAND:= ((((bmpAND.bmWidth*bmpAND.bmBitsPixel)+31) and not 31) shr 3)*bmpAND.bmHeight; RawDataSizeXOR:= ((((bmpXOR.bmWidth*BitCountPerPixel)+31) and not 31) shr 3)*bmpXOR.bmHeight; RawDataSize:= RawDataSizeAND + RawDataSizeXOR; PalSize:=0; if(BitCountPerPixel<=8) then PalSize:= (1 shl BitCountPerPixel) shl 2; // RGBQUAD 4 AllSize:= sizeof(TICONDIR)+sizeof(BITMAPINFOHEADER)+PalSize+RawDataSizeAND+RawDataSizeXOR; Width := bmpAND.bmWidth; Height := bmpAND.bmHeight; // Si Buffer es nulo, termino retornando el tamaño necesario del buffer en Bytes // en caso contrario ejecuto... if(Buffer <> nil) then begin Result:= 0; if Size >= AllSize then begin // Convierto Buffer a ICONDIR IconDir:= PICONDIR(Buffer); // Obtengo los Bits de cada parte Mask y Color bmiICON:= PBITMAPINFO(DWORD(FileIconMem) + sizeof(TICONDIR)); lpBitsXOR:= PBITMAPINFO(DWORD(FileIconMem) + sizeof(TICONDIR) + sizeof(BITMAPINFOHEADER) + PalSize); lpBitsAND:= PBITMAPINFO(DWORD(lpBitsXOR) + RawDataSizeXOR); // Preparo la cabecera del Icon IconDir.idReserved:= 0; IconDir.idType:= 1; IconDir.idCount:= 1; IconDir.idEntries[0].bWidth:= Width; IconDir.idEntries[0].bHeight:= Height; IconDir.idEntries[0].bColorCount:= NColors(BitCountPerPixel); IconDir.idEntries[0].bReserved:= 0; IconDir.idEntries[0].wPlanes:= 0; // Color Planes IconDir.idEntries[0].wBitCount:= 0;//BitCountPerPixel; // Bits per pixel IconDir.idEntries[0].dwBytesInRes:= AllSize-sizeof(TICONDIR); // How many bytes in this resource? IconDir.idEntries[0].dwImageOffset:= sizeof(TICONDIR); // Where in the file is this image? IconImage:= PICONIMAGE(bmiICON); ZeroMemory(IconImage, sizeof(BITMAPINFOHEADER)); IconImage.icHeader.biSize:= sizeof(BITMAPINFOHEADER); IconImage.icHeader.biWidth:= Width; IconImage.icHeader.biHeight:= Height; IconImage.icHeader.biPlanes:= 1; IconImage.icHeader.biBitCount:= BitCountPerPixel; IconImage.icHeader.biSizeImage:= RawDataSize; // Preparo BITMAPINFOHEADER para la Mascara (bmiAND) CopyMemory(@bmiAND, bmiICON, sizeof(BITMAPINFOHEADER)); bmiAND.bmiHeader.biSizeImage:= RawDataSizeAND; bmiAND.bmiHeader.biBitCount:= 1; bmiAND.bmiHeader.biClrUsed:= 1; bmiAND.bmiHeader.biClrImportant:= 1; // Recupero los bits de cada hBitmap del icono GetDIBits(hDC, IconInfo.hbmColor, 0, Height, lpBitsXOR, bmiICON^, DIB_RGB_COLORS); GetDIBits(hDC, IconInfo.hbmMask, 0, Height, lpBitsAND, bmiAND, DIB_RGB_COLORS); // Sumo las dos alturas de ambos bitmaps del icono IconImage.icHeader.biHeight:= Height*2; // Salida Result:= AllSize; end; end else begin Size:= AllSize; Result:= 1; end; ReleaseDC(0, hDC); end;
Por no extenderme demasiado termino aquí y dejo para el siguiente mensaje los sencillos ejemplos de uso de estas funciones.
Saludos.
#12
Posted 04 January 2010 - 05:20 AM
Bien, toca ahora mostrar el uso de las funciones anteriores. Voy a dar dos ejemplos básicos. El primero muestra como guardar un archivo de icono a partir de un Handle, mientras que el segundo lo asigna a un objeto TIcon con el que podremos trabajar tranquilamente en delphi:
Y su homóloga:
Y su homóloga:
Todo el código lo recojo en una unit que cuelgo del primer mensaje del hilo.
Espero haber sido de utilidad.
Saludos.
delphi
function HIconToFile(hIcon: THandle; FileName: String): Boolean; var FileIconMem: Pointer; Size: integer; hFile: Cardinal; begin Result:= false; FileIconMem:= hIconToMem(hIcon, 32, Size); hFile := CreateFile(PCHAR(FileName), GENERIC_WRITE, 0, nil, OPEN_ALWAYS, FILE_FLAG_WRITE_THROUGH, 0); if(_lwrite(hFile, FileIconMem, Size) = Size) then Result:= true; CloseHandle(hFile); VirtualFree(FileIconMem, 0, MEM_RELEASE); end;
Y su homóloga:
delphi
function HIconToFile2(hIcon: THandle; FileName: String): Boolean; var FileIconMem: Pointer; Size: integer; hFile: Cardinal; begin Result:= false; if _hIconToMem(hIcon, 32, nil, Size)<>0 then begin // Reservo un buffer. FileIconMem:= AllocMem(Size); _hIconToMem(hIcon, 32, FileIconMem, Size); hFile := CreateFile(PCHAR(FileName), GENERIC_WRITE, 0, nil, OPEN_ALWAYS, FILE_FLAG_WRITE_THROUGH, 0); if(_lwrite(hFile, FileIconMem, Size) = Size) then Result:= true; CloseHandle(hFile); end; end;
delphi
procedure HIconToTicon(Hicon: THandle; Icon: TIcon); var Stream: TMemoryStream; FileIconMem: Pointer; Size: integer; begin // Creo un MemoryStream Stream:= TMemoryStream.Create; // Leo el mapa del hIcon deseado FileIconMem:= hIconToMem(hIcon, 32, Size); // Lo paso al MemoryStream Stream.WriteBuffer(FileIconMem^, Size); // Libero el mapa VirtualFree(FileIconMem, 0, MEM_RELEASE); Stream.Position:= 0; // Para situar le puntero de lectura al principio del Stream Icon.LoadFromStream(Stream); // Cargo la imagen del Stream en el TIcon Stream.Free; // Libero el Stream end;
Y su homóloga:
delphi
procedure HIconToTIcon2(Hicon: THandle; Icon: TIcon); var Stream: TMemoryStream; FileIconMem: Pointer; Size: integer; begin // Creo un MemoryStream Stream:= TMemoryStream.Create; // Leo el mapa del hIcon deseado if _hIconToMem(hIcon, 32, nil, Size)<>0 then begin // Reservo un buffer. FileIconMem:= AllocMem(Size); _hIconToMem(hIcon, 32, FileIconMem, Size); // lo paso al MemoryStream Stream.WriteBuffer(FileIconMem^, Size); // Libero el mapa FreeMem(FileIconMem); // Creo el icono con la imagen del hIcon Stream.Position:= 0; // Para situar le puntero de lectura al principio del Stream Icon.LoadFromStream(Stream); // Cargo la imagen del Stream en el TIcon Stream.Free; // Libero el Stream end; end;
Todo el código lo recojo en una unit que cuelgo del primer mensaje del hilo.
Espero haber sido de utilidad.
Saludos.
#13
Posted 04 January 2010 - 09:45 AM
Muchas gracias por el tutorial amigo escafandra, muy interesante y muy ilustrativo.
Salud OS
Salud OS