9 de junio de 2015

Convertir BMP a ICONO - Parte 2

Artículo original: Convert BMP to ICON - Part 2
http://weblogs.foxite.com/vfpimaging/archive/2007/08/14/4528.aspx
Autor: Cesar Ch.
Traducido por: Luis María Guayán


Aquí está un código que convierte un Bitmap GDI u objeto imagen a un archivo de ícono .ICO, conservando la misma calidad que la imagen original.

En un envio anterior (Nota del traductor: Traducido en este Blog como Convertir BMP a ICONO - Parte 1), mostré el modo más fácil de convertir un Bitmap a ícono, usando la función API OleCreatePictureIndirect. Con muy poco código podríamos lograr archivos .ICO, pero lamentablemente, los resultados eran solo imágenes de 4bpp (bit por pixel), con 16 colores. Sospecho que Windows no proporciona apoyo a más de 16 colores. En una pequeña búsqueda en la web, encontré el siguiente tip de Cetin Basoz en FoxyClasses tip, que da una posible razón de los 16 colores de los iconos: "Aquí está el truco al hacer sus propios iconos: Las imágenes utilizadas deben ser solo de 16 colores. Si utiliza 256 colores, el ícono del logotipo de Fox será mostrado en lugar del suyo! También debe incluir el tamaño correcto de imágenes en el icono". Esto parece ser un antiguo consejo, ya que en mis pruebas en formularios de VFP trabajan muy bien con iconos de más de 16 colores.

Afortunadamente, VFP nos permite utilizar colores hasta 32bpp, de modo que podemos convertir cualquier imagen a un icono. La limitación es que VFP soporta sólo imágenes de iconos de 16x16 o 32x32 píxeles.

Agradecimiento especial a Sergey Karimov

Mi punto de partida fue un código de Sergey Karimov, que publicó en UT el año pasado. Su código original trata con BMPs físicos, convirtiéndolos a archivos .ICO. Pero mi verdadera necesidad era hacer éste sin acceso de disco adicional.

La fuente principal de la información sobre .ICO que encontré en la web fue un gran artículo de John Hornick, en MSDN, "Iconos en Win32". Para crear mis iconos, todo lo que tuve que debía hacer es seguir con cuidado todas las instrucciones proporcionadas allí, obviamente con una gran ayuda de Anatolyi Mogylevetz, en algunos de sus grandes artículos: "Retrieving information about the specified icon" y "Converting image file to .ICO file".

Abajo está mi función BMP2ICO, que crea archivos físicos .ICO de la alta calidad. En la primera parte del código de abajo encontrará el llamado al programa que cambia el tamaño a la imagen a 32x32 pixeles. El código llamado es muy fácil para entender, y puede adaptarlo según sus necesidades. Pero la parte principal es mucho más complicada, porque este tiene que crear muchas estructuras en C siguiendo las instrucciones de "Iconos en Win32".

IMPORTANTE

Requiere VFP9 y GdiPlusX para funcionar.

¡Por favor asegúrese que tiene la última versión!

http://www.codeplex.com/VFPX/Wiki/View.aspx?title=GDIPlusX&referringTitle=Home

LOCAL lcPict, lcIconFile
lcPict = GETPICT("bmp")
IF EMPTY(lcPict)
   RETURN
ENDIF
lcIconFile = "c:\_" + JUSTSTEM(JUSTFNAME(lcPict)) + ".ico"

* Make sure we have initialized the GDIPlusX library
* http://www.codeplex.com/VFPX/Wiki/View.aspx?title=GDIPlusX 
IF VARTYPE(_SCREEN.System) <> "O"
   ADDPROPERTY(_SCREEN,"System",NEWOBJECT("xfcSystem",LOCFILE("system.vcx")))
ENDIF

LOCAL loBmp as xfcBitmap
loBmp = _SCREEN.System.Drawing.Bitmap.FromFile(lcPict)

LOCAL loResized as xfcBitmap
loResized = loBmp.GetThumbnailImage(32,32)

=BMP2ICO(loResized, lcIconFile, .T.)
RETURN

 
* Function: BMP2ICO
* Parameters: toBmp (xfcImage), tcFileName
* Related Articles: 
* Icons in Win32 - by John Hornick http://msdn2.microsoft.com/en-us/library/ms997538.aspx 
* Retrieving information about the specified icon http://www.news2news.com/vfp/?example=206&function=331 
* Converting image file to .ICO file http://www.news2news.com/vfp/?example=503&function=331 
* Special Thanks:
* Sergey Karimov, Ontario, Canada
* Anatolyi Mogylevetz

FUNCTION BMP2ICO(toBmp AS xfcBitmap, tcFileName AS Character, tlChangePixFormat AS Boolean)

* Make sure we have initialized the GDIPlusX library
* http://www.codeplex.com/VFPX/Wiki/View.aspx?title=GDIPlusX 
IF VARTYPE(_SCREEN.System) <> "O"
   ADDPROPERTY(_SCREEN,"System",NEWOBJECT("xfcSystem",LOCFILE("system.vcx")))
ENDIF
 
LOCAL hIcon, lcBuffer, lnWidth, lnHeight, lnBitsPerPixel
LOCAL lhColorBitmap, lhMaskBitmap 
LOCAL loBmp as xfcBitmap
lnWidth = toBmp.Width
lnHeight = toBmp.Height

IF tlChangePixFormat
* Convert the original bitmap to ensure better quality and compatibility
   loBmp = _SCREEN.System.Drawing.Bitmap.New(toBmp, lnWidth, lnHeight) && This is to transform the Bitmap to 32bppARGB
   lnBitsPerPixel = 32 && 32bpp ARGB
ELSE
   loBmp = toBmp
   lnBitsPerPixel = loResized.GetPixelFormatSize(loResized.PixelFormat)
ENDIF
 
* Obtain hIcon from Bitmap
hIcon = loBmp.GetHicon()
 
* API Declarations needed
DECLARE SHORT DestroyIcon IN user32 INTEGER hIcon 
DECLARE INTEGER GetIconInfo IN user32 INTEGER hIcon, STRING @piconinfo 
DECLARE INTEGER GetDIBits IN gdi32; 
   INTEGER hdc, INTEGER hbmp, INTEGER uStartScan,; 
   INTEGER cScanLines, INTEGER lpvBits, STRING @lpbi, INTEGER uUsage 

DECLARE INTEGER CreateCompatibleDC IN gdi32 INTEGER hdc
DECLARE INTEGER DeleteDC IN gdi32 INTEGER hdc


DECLARE INTEGER ReleaseDC IN user32 INTEGER hwnd, INTEGER hdc
DECLARE INTEGER GetWindowDC IN user32 INTEGER hwnd
DECLARE RtlZeroMemory IN kernel32 As ZeroMemory INTEGER dest, INTEGER numBytes

DECLARE INTEGER SelectObject IN gdi32 INTEGER hdc, INTEGER hObject
DECLARE INTEGER DeleteObject IN gdi32 INTEGER hObject

DECLARE INTEGER GlobalFree IN kernel32 INTEGER hMem
DECLARE INTEGER GlobalAlloc IN kernel32 INTEGER wFlags, INTEGER dwBytes 



* ICONINFO structure
*| typedef struct _ICONINFO { 
*| BOOL fIcon; 0:4 
*| DWORD xHotspot; 4:4 
*| DWORD yHotspot; 8:4 
*| HBITMAP hbmMask; 12:4 
*| HBITMAP hbmColor; 16:4 
*| } ICONINFO; total bytes = 20

#DEFINE ICONINFO_SIZE 20 
lcBuffer = REPLICATE(CHR(0), ICONINFO_SIZE) 
= GetIconInfo(hIcon, @lcBuffer) 
lhColorBitmap = CTOBIN(SUBSTR(lcBuffer,17,4),"4rs")
lhMaskBitmap = CTOBIN(SUBSTR(lcBuffer,13,4),"4rs") 
= DestroyIcon(hIcon) 
 
 
* DIB BITMAPINFOHEADER. 
* Only the following members are used: biSize, biWidth, biHeight, biPlanes, biBitCount, biSizeImage
*!* typedef struct tagBITMAPINFOHEADER{
*!* DWORD biSize; 
*!* LONG biWidth; 
*!* LONG biHeight; 
*!* WORD biPlanes; 
*!* WORD biBitCount; 
*!* DWORD biCompression; 
*!* DWORD biSizeImage; 
*!* LONG biXPelsPerMeter; 
*!* LONG biYPelsPerMeter; 
*!* DWORD biClrUsed; 
*!* DWORD biClrImportant; 
*!* } BITMAPINFOHEADER, *PBITMAPINFOHEADER

#DEFINE DIB_RGB_COLORS 0 
#DEFINE RGBQUAD_SIZE 4
#DEFINE BHDR_SIZE 40
#DEFINE GMEM_FIXED 0
#DEFINE BI_RGB 0
LOCAL lcBIHdr, lcBInfo, lcRgbQuad, lnRgbQuadSize, lpBitsArray, lnBitsSize1, lnBitsSize2
LOCAL lnBytesPerScan
 
* Obtain the XOR Bitmap
m.lnBytesPerScan = INT((m.lnWidth * m.lnBitsPerPixel)/8)
IF MOD(m.lnBytesPerScan, 4) # 0
   m.lnBytesPerScan = m.lnBytesPerScan + 4 - MOD(m.lnBytesPerScan, 4)
ENDIF

m.lnBitsSize1 = m.lnHeight * m.lnBytesPerScan
m.lcBIHdr = BINTOC(BHDR_SIZE ,"4RS") + ; && biSize
   BINTOC(m.lnWidth,"4RS") + ; && biWidth
   BINTOC(m.lnHeight, "4RS") + ; && biHeight
   BINTOC(1, "2RS") + ; && biPlanes 
   BINTOC(lnBitsPerPixel, "2RS") + ; && biBitCount 
   BINTOC(BI_RGB, "4RS") + ; && biCompression
   BINTOC(lnBitsSize1, "4RS") + ; && biSizeImage
   REPLICATE(CHR(0), 16)

IF m.lnBitsPerPixel <= 8
   m.lnRgbQuadSize = (2^m.lnBitsPerPixel) * RGBQUAD_SIZE
   m.lcRgbQuad = REPLICATE(CHR(0), m.lnRgbQuadSize)
ELSE
   m.lcRgbQuad = ""
ENDIF

m.lcBInfo = m.lcBIHdr + m.lcRgbQuad
m.lpBitsArray = GlobalAlloc (GMEM_FIXED, m.lnBitsSize1)
= ZeroMemory (m.lpBitsArray, m.lnBitsSize1)
 
LOCAL lhdc, lhMemDC
m.lhDC = GetWindowDC(_screen.HWnd)
m.lhMemDC = CreateCompatibleDC(m.lhDC)
= ReleaseDC (_screen.HWnd, m.lhDC)
= SelectObject(lhMemDC, lhColorBitmap) 
= GetDIBits (m.lhMemDC, m.lhColorBitmap, 0, m.lnHeight, m.lpBitsArray , @lcBInfo, DIB_RGB_COLORS)
LOCAL lqColorBinary, lqMaskBinary
m.lqColorBinary = SYS(2600, m.lpBitsArray, m.lnBitsSize1)
= DeleteObject (m.lhColorBitmap)
= GlobalFree(m.lpBitsArray)
 
* Obtain the AND Mask
* The icAND member contains the bits for the monochrome AND mask. 
* The number of bytes in this array is determined by examining the icHeader member, and assuming 1bpp.
* The dimensions of this bitmap must be the same as the dimensions of the XOR mask.
* The AND mask is applied to the destination using the AND operation, to preserve or remove destination pixels before applying the XOR mask.

LOCAL lpBitsArray2, lcBInfo2
m.lnBitsSize2 = m.lnHeight * INT((m.lnWidth * 1)/8) && 1BPP
m.lpBitsArray2 = GlobalAlloc (GMEM_FIXED, m.lnBitsSize2)
= ZeroMemory (m.lpBitsArray2, m.lnBitsSize2)
= SelectObject(lhMemDC, lhMaskBitmap) 
m.lcBInfo2 = BINTOC(BHDR_SIZE ,"4RS") + ; && biSize
   BINTOC(m.lnWidth,"4RS") + ; && biWidth
   BINTOC(m.lnHeight, "4RS") + ; && biHeight
   BINTOC(1, "2RS") + ; && biPlanes 
   BINTOC(1, "2RS") + ; && CHR(INT(1/256))) + && biBitCount 
   BINTOC(BI_RGB, "4RS") + ; && biCompression
   BINTOC(lnBitsSize2, "4RS") + ; && biSizeImage
   REPLICATE(CHR(0), 16)

= GetDIBits (m.lhMemDC, m.lhMaskBitmap, 0, m.lnHeight, m.lpBitsArray2 , @lcBInfo2, DIB_RGB_COLORS)
m.lqMaskBinary = SYS(2600, m.lpBitsArray2, m.lnBitsSize2)
= DeleteObject (m.lhMaskBitmap)
= GlobalFree(m.lpBitsArray2)
= DeleteDC (m.lhMemDC)
 
LOCAL lcIconDir, lnOffset

*!* typedef struct
*!* {
*!* WORD idReserved; // Reserved (must be 0)
*!* WORD idType; // Resource Type (1 for icons)
*!* WORD idCount; // How many images?
*!* ICONDIRENTRY idEntries[1]; // An entry for each image (idCount of 'em)
*!* } ICONDIR, *LPICONDIR

lcIconDir = BINTOC(0, "2RS") + ; && 0 reserved
   BINTOC(1, "2RS") + ; && 2 type
   BINTOC(1, "2RS") && 4 Number of Icons in this file

lnOffset = LEN(lcIconDir)+16


*!* typedef struct
*!* {
*!* BYTE bWidth; // Width, in pixels, of the image
*!* BYTE bHeight; // Height, in pixels, of the image
*!* BYTE bColorCount; // Number of colors in image (0 if >=8bpp)
*!* BYTE bReserved; // Reserved ( must be 0)
*!* 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

LOCAL lnColors, lcIconDirEntry
lnColors = IIF(lnBitsPerPixel > 8, 0, 4*2^lnBitsPerPixel)
lcIconDirEntry = CHR(lnWidth) + ; && 0 width of the image, in pixels
   CHR(lnHeight) + ; && 1 height of image, in pixels (OR & AND bmps)
   CHR(lnColors) + ; && 2 Number of colors in image (0 if >=8bpp)
   CHR(0) + ; && 3 reserved
   BINTOC(1,"2RS") + ; && 4 Number of Planes
   BINTOC(lnBitsPerPixel,"2RS") && 6 Bits per pixel

lcBInfo = STUFF(lcBInfo,9,1,CHR(lnHeight*2)) && height of img, in pixels (OR & AND bmps)
&& The biHeight member of the icHeader structure represents the combined height of the XOR and AND masks
lcBInfo = STUFF(lcBInfo,21,4, BINTOC((lnBitsSize1 + lnBitsSize2), "4RS")) && Size of Image (OR & AND bitmaps)

*!* typdef struct
*!* {
*!* BITMAPINFOHEADER icHeader; // DIB header
*!* RGBQUAD icColors[1]; // Color table
*!* BYTE icXOR[1]; // DIB bits for XOR mask
*!* BYTE icAND[1]; // DIB bits for AND mask
*!* } ICONIMAGE, *LPICONIMAGE

lqBinary = lcBInfo + lqColorBinary + lqMaskBinary
lcIconDirEntry = lcIconDirEntry + BINTOC(LEN(lqBinary),"4RS") + ; && 8 Size of img area
   BINTOC(lnOffset,"4RS") && 12 offset to image area

* Finally, save the icon to the disk

* This still needs some error checking !
LOCAL lhFile
lhFile= FCREATE(tcFileName)
IF lhFile<1
   =MESSAGEBOX("Cannot create file " + tcFileName + " !")
   RETURN .F.
ENDIF

=FWRITE(lhFile, (lcIconDir + lcIconDirEntry))
=FWRITE(lhFile, lqBinary)
=FCLOSE(lhFile)
RETURN

No hay comentarios. :

Publicar un comentario