2007-08-14

Convert BMP to Icon - Part 2

here's a code that converts a gdi+ bitmap or image object to an icon .ico file, keeping the same quality as the source image.


in a previous post, i showed the simplest way to convert a bitmap to icon, using the olecreatepictureindirect api function. with very few code we could get .ico files, but unfortunately, the results were always 4bpp (bits per pixel) images, with 16 colors. i suspect that windows didn't provide support for more than 16 colors. in a short research on the web, i found the following tip in foxyclasses tips from cetin basoz, that gives a possible reason for the 16 color icon: "here's the trick when you make your own icons: the images used must be 16 colors only. if you use 256 colors, the fox logo icon will be shown instead of your own! you must also include the correct size images in the icon". this seems to be an old tip, because in my tests vfp forms work nice with icons of more than 16 colors.


fortunately, vfp allows us to use up to 32bpp colors, so we can convert any image to an icon. the limitation is that vfp supports only 16x16 or 32x32 pixel images.
 


special thanks to sergey karimov


my starting point was a code from sergey karimov, that he posted in ut last year. his original code dealt with physical bmps, converting them to .ico files. but my real need was to do this without additional disk access.


the main source of .ico information that i found on the web was a great article from john hornick, in msdn, "icons in win32". to create my icons, all i had to do was to follow carefully all the instructions provided there, obviously with a great help from anatolyi mogylevetz, in some of his great articles: "retrieving information about the specified icon" and "converting image file to .ico file".


below is my bmp2ico function, that creates high quality .ico physical files. in the first part of the code below you'll find the caller program, that also resizes the image to 32x32 pixels. the caller code is very easy to understand, and you can adapt it to fit your needs. but the main part is much more complicated, because it needs to create many c structures following the instructions from "icons in win32".


 

important
requires vfp9 and gdiplusx to run. 
please make sure that you have the latest version!
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"
   do locfile("system.app")
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"
   do locfile("system.app")
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

2 comments:

  1. Este articulo esta traducido al español en PortalFox:


    -- Convertir BMP a ICONO - Parte 2 --

    http://www.portalfox.com/article.php?sid=2539


    ReplyDelete
  2. Peter de ValençaMay 5, 2008 at 2:46 PM

    Hi Cesar,

    Is there a way to make the icon transparant?
     
    Hi Peter,
    Yes, it is totally possible. Originally the .NET classes do not allow this, but we've already figured how to do it. I still need to find some time to add a new method to the Icon class, maybe "AddMask()"
    As you requested, I hope to work on it in the next days.
    Regards
    Cesar

    ReplyDelete