That Calvin's post is dedicated to visual studio developers.
As in the comments there were some people asking the code for VFP, below is an adapted version for VFP users, using, of course, GdiplusX to help us extracting the icons from the resource.
There's still more to say about this, but in this first moment, I'm putting just the code to retrieve the embedded bitmap from the visual studio dll that calvin refers to. Below is the most relevant part of the original code in VB .net from Calvin, very easilly converted to VFP, using the GdiplusX library.
Picture 1: Relevant Source code in VB .NET
Requires VFP9 and GdiplusX to run.
https://github.com/VFPX/GDIPlusX
* Init GdiPlusX DO LOCFILE("System.app") #DEFINE load_library_as_image_resource 0x20 #DEFINE load_library_as_datafile 2 DECLARE INTEGER LoadLibraryEx IN kernel32; string lpfilename, INTEGER hmodule, INTEGER dwflags DECLARE INTEGER LoadBitmap IN user32; INTEGER hmodule, INTEGER hresinfo DECLARE INTEGER FreeLibrary IN kernel32 long hmodule DECLARE INTEGER DeleteObject IN win32api INTEGER hobject LOCAL lhmodule, lhres, lcResFile * lcResFile = ADDBS(GETENV("ProgramFiles")) + ; "microsoft visual studio 9.0\common7\ide\msenv.dll" lcResFile = ADDBS(GETENV("ProgramFiles")) + ; "microsoft visual studio 10.0\common7\ide\msenv.dll" IF NOT FILE(lcResFile) lcResFile = GETFILE("dll") ENDIF lhModule = LoadLibraryEx(lcResFile, ; 0, load_library_as_image_resource + load_library_as_datafile) lhRes = LoadBitmap(lhModule, 0x4f4) IF lhRes > 0 LOCAL loBmp AS xfcBitmap WITH _Screen.System.drawing loBmp = .Bitmap.FromhBitmap(lhres) loBmp.Save("c:\ResourceImage.bmp", .Imaging.ImageFormat.Bmp) ENDWITH ELSE MESSAGEBOX("Could not load the file") ENDIF
Picture 3: a part of the extracted image expanded 8 times
Cool ! but still not ready to be used in our VFP forms.
As that bitmap contains many icons in the same file, i'll use some gdi+ stuff to separate each of them, saving in a different file. we have to know the dimensions of each image, so i'll take the smallest value between the width and height, and use a loop to crop each icon image separately. the backgrounds also need to be treated, as i'll comment below.
For vs users, Calvin used a totally different technique to separate the images, using the listview and the treeview controls from VS. If someone could show me how to chieve this in vfp using his technique... It would be great !
Each image is saved in two different formats:
1 - bmp of 24 bits, with white background, to be used in vfp buttons. the white background is rgb(255,255,255). for safety purposes, i also changed all original whites from the icons to rgb(254,254,254). this way, vfp will not make these parts of the images as transparent.
2 - png of 32bits, with the background color set as transparent
There is some more complex code that I used to crop the images. It's all full of comments, and I hope, it's easy to understand.
************************************************************************************************************* program: ExtractIconsFromResource2.prg
* author : Cesar Ch
*
* extracts all 16x16 icons stored in dlls
* extracts the icons from the big collection pictures obtained from the resource
* applies effects to the image to keep transparencies
*
* sample totally based on calvin hsia's article from his blog
* "extract treeview or listview imagelist icons from a dll"
* http://blogs.msdn.com/calvin_hsia/archive/2007/11/06/5948486.aspx?commentposted=true#commentmessage
*
* prerequisites:
* vfp9 and GdiplusX library
************************************************************************************************************
DO LOCFILE("system.app")
#DEFINE load_library_as_image_resource 0x20
#DEFINE load_library_as_datafile 2
DECLARE INTEGER loadlibraryex IN kernel32;
STRING lpfilename,;
INTEGER hfile,;
INTEGER dwflags
DECLARE LONG freelibrary IN kernel32 LONG hmodule
DECLARE INTEGER loadbitmap IN user32;
INTEGER hinstance,;
INTEGER lpbitmapname
DECLARE INTEGER deleteobject IN win32api INTEGER hobject
WITH _SCREEN.SYSTEM.drawing
LOCAL lcfile, lcshortname, lciconfile
LOCAL hmodule, hres
LOCAL N, lnwidth, lnheight
LOCAL loicon AS xfcbitmap
LOCAL lobmp AS xfcbitmap
LOCAL loborderclr AS xfccolor
* these objects will be used only if the output is bmp
LOCAL locloned AS xfcbitmap
LOCAL locolormap AS xfccolormap
locolormap = .imaging.colormap.new()
LOCAL loattr AS xfcimageattributes
m.loattr = .imaging.imageattributes.new()
lcfile = "c:\program files\microsoft visual studio 9.0\common7\ide\msenv.dll" + CHR(0)
lcshortname = "c:\" + JUSTFNAME(lcfile)
hmodule = loadlibraryex(lcfile, 0, load_library_as_image_resource + load_library_as_datafile)
hres = loadbitmap(hmodule, 0x4f4)
IF hres = 0
LOOP
ENDIF
lobmp = .BITMAP.fromhbitmap(hres)
lobmp.SAVE(lcshortname + "_allicons.png", .imaging.imageformat.png)
deleteobject(hres)
lnheight = lobmp.HEIGHT
lnwidth = lnheight
IF lnheight > 16 && this is a big image, so it's not the icon we're interested in
LOOP
ENDIF
* get the quantity of bits per pixels of the image.
* if we have an indexed format (< 24), we wont create the transparencies manually.
LOCAL lnpixfmtsize
lnpixfmtsize = lobmp.getpixelformatsize(lobmp.pixelformat)
* get the top left pixel color, presuming this color will be converted
* to transparent
loborderclr = lobmp.getpixel(0,0)
* make a copy of the bitmap for further processing as bmp
locloned = lobmp.CLONE()
*-* beginning of the png cropping
* save the icons as pngs with transparent background
IF lnpixfmtsize >= 24 && the background is not white, so
lobmp.maketransparent(loborderclr)
ENDIF
FOR N = 0 TO (lobmp.WIDTH / lnwidth) - 1
lciconfile = lcshortname + "_icon_" + TRANSFORM(N) + ".png"
loicon = lobmp.CLONE(.rectangle.new(N * lnwidth, 0, lnwidth, lnheight))
IF VARTYPE(loicon) = "o"
loicon.SAVE(lciconfile, .imaging.imageformat.png)
ENDIF
ENDFOR
* restore the original bitmap object for the bmp transformations
lobmp = locloned.CLONE()
*-* end of png cropping
*-* beginning of the bmp cropping
IF loborderclr <> .COLOR.white AND ;
lnpixfmtsize >= 24 && the background is not white, so
&& let's make some changes to adapt the icon to have a white
&& background, that will appear transparent in buttons
* convert original whites rgb(255,255,255) to off white - rgb(254,254,254)
* this way, the whites will remain without the need of a mask
locolormap.oldcolor = .COLOR.white
locolormap.newcolor = .COLOR.fromargb(255,254,254,254)
loattr.setremaptable(locolormap)
logfx = .graphics.fromimage(lobmp)
lorect = lobmp.getbounds()
logfx.drawimage(lobmp, m.lorect, m.lorect, .graphicsunit.PIXEL, loattr)
* next step, convert the borders to pure white, rgb(255,255,255) that will become transparent in buttons
locolormap.oldcolor = loborderclr
locolormap.newcolor = .COLOR.white
loattr.setremaptable(locolormap)
logfx.drawimage(lobmp, m.lorect, m.lorect, .graphicsunit.PIXEL, loattr)
ENDIF
* continue, cropping each icon from the big image
FOR N = 0 TO (lobmp.WIDTH / lnwidth) - 1
lciconfile = lcshortname + "_icon_" + TRANSFORM(N) + ".bmp"
loicon = lobmp.CLONE(.rectangle.new(N * lnwidth, 0, lnwidth, lnheight), .imaging.pixelformat.format24bpprgb)
IF VARTYPE(loicon) = "o"
loicon.SAVE(lciconfile, .imaging.imageformat.bmp)
ENDIF
ENDFOR
*-* end of the bmp cropping
freelibrary(hmodule)
ENDWITH
But there's still one important problem that I wasn't able to solve yet: to list all available resources. so, for the case i wanted to extract the biggest quantity of available icons in all resource files of my computer, the provisory solution i've found was to create a loop, from 0 (zero) to 65535, trying to retrieve the image resource.
I've just based myself in another old post of mine, extract icons from exe, dll and ico files with gdiplusx , and created a new script for extracting all available 16x16 icons in all resources from a specific folder, checking also all the subdirectories. the final result is tons of icons, all stored in just one folder, "c:\resource icons", in two versions, one in png with the transparent background, and another version stored in the format of bmp of 24 bits, with white background, that is the format best supported by vfp for our command buttons.
When you run this last sample, depending on the folder that you choose, and the remaining subfolders, the resulting process may be really long, so i ask you to be patient, but i ensure you it's worth. when finished, open the folder "c:\resource icons". my search in drive c of my computer, resulted in more than 25000 different icons !
Enjoy !
************************************************************************************************************ * PROGRAM: ExtractIconsFromResource3.Prg * AUTHOR : Cesar Chalom * * Extracts all 16x16 icons stored in DLLs or EXE files * Explores recursively in all the directory and subdirectories selected * Extracts the icons from the big collection pictures obtained from the resource * * Sample totally based on Calvin Hsia's article from his blog * "Extract TreeView or ListView ImageList icons from a DLL" * http://blogs.msdn.com/calvin_hsia/archive/2007/11/06/5948486.aspx?CommentPosted=true#commentmessage * * Prerequisites: * VFP9 and GdiPlusX library ************************************************************************************************************ DO LOCFILE("System.prg") #DEFINE LOAD_LIBRARY_AS_IMAGE_RESOURCE 0X20 #DEFINE LOAD_LIBRARY_AS_DATAFILE 2 DECLARE INTEGER LoadLibraryEx IN kernel32; STRING lpFileName,; INTEGER hFile,; INTEGER dwFlags DECLARE LONG FreeLibrary IN Kernel32 LONG hModule DECLARE INTEGER LoadBitmap IN user32; INTEGER hInstance,; INTEGER lpBitmapName DECLARE INTEGER DeleteObject IN WIN32API INTEGER hObject * Create destination directory to receive the extracted icons lcDir = "C:\ResourceIcons" IF NOT DIRECTORY(lcDir) MKDIR (lcDir) ENDIF * Create the cursor that will store the valid file locations CREATE CURSOR Recursive (cFile c(250)) LOCAL lcSelectedDir lcSelectedDir = GETDIR() IF EMPTY(lcSelectedDir) WAIT WINDOW "Invalid Directory" RETURN .F. ENDIF WAIT WINDOW "Retrieving folders information" NOWAIT * Scan through the selected and the subfolders =Recurse(lcSelectedDir) WITH _SCREEN.SYSTEM.Drawing LOCAL lcFile, lcShortName, lcIconFile, lnTotRec LOCAL lnOldPercentage, lnPercentage LOCAL hModule, hRes LOCAL N, lnWidth, lnHeight LOCAL loIcon AS xfcBitmap LOCAL loBmp AS xfcBitmap LOCAL loBorderClr AS xfcColor * These objects will be used only if the output is BMP LOCAL loCloned AS xfcBitmap LOCAL loColorMap AS xfcColorMap loColorMap = .Imaging.ColorMap.New() LOCAL loAttr AS xfcImageAttributes m.loAttr = .Imaging.ImageAttributes.New() STORE 0 TO lnPercentage, lnOldPercentage lnTotRec = RECCOUNT() SCAN lcFile = ALLTRIM(Recursive.cFile) + CHR(0) lcShortName = JUSTFNAME(lcFile) hModule = LoadLibraryEx(lcFile, 0, LOAD_LIBRARY_AS_IMAGE_RESOURCE + LOAD_LIBRARY_AS_DATAFILE) FOR x = 0 TO 0xffff hRes = LoadBitmap(hModule, x) && 0x4f4) IF hRes = 0 LOOP ENDIF lcNewFile = ADDBS(lcDir) + lcShortName loBmp = .BITMAP.FromHbitmap(hRes) loBmp.Save(lcNewFile + "_AllIcons_r" + TRANSFORM(x) + ".Png", .Imaging.ImageFormat.Png) DeleteObject(hRes) lnHeight = loBmp.HEIGHT lnWidth = lnHeight IF lnHeight > 16 && This is a big image, so it's not the icon we're interested in LOOP ENDIF * Get the quantity of Bits per Pixels of the image. * If we have an indexed format (< 24), we wont create the transparencies manually. LOCAL lnPixFmtSize lnPixFmtSize = loBmp.GetPixelFormatSize(loBmp.PixelFormat) * Get the top left pixel color, presuming this color will be converted * to transparent loBorderClr = loBmp.GetPixel(0,0) * Make a copy of the bitmap for further processing as BMP loCloned = loBmp.Clone() *-* BEGINNING OF THE PNG CROPPING * Save the Icons as PNGs with transparent background IF lnPixFmtSize >= 24 && The background is not White, so loBmp.MakeTransparent(loBorderClr) ENDIF FOR N = 0 TO (loBmp.WIDTH / lnWidth) - 1 lcIconFile = lcNewFile + TRANSFORM(x) + "_Icon_" + TRANSFORM (N) + ".png" loIcon = loBmp.CLONE(.Rectangle.New(N * lnWidth, 0, lnWidth, lnHeight)) IF VARTYPE(loIcon) = "O" loIcon.SAVE(lcIconFile, .Imaging.ImageFormat.Png) ENDIF ENDFOR * Restore the original Bitmap object for the BMP transformations loBmp = loCloned.Clone() *-* END OF PNG CROPPING *-* BEGINNING OF THE BMP CROPPING IF loBorderClr <> .Color.White AND ; lnPixFmtSize >= 24 && The background is not White, so && let's make some changes to adapt the icon to have a white && background, that will appear transparent in buttons * Convert original Whites RGB(255,255,255) to OFF WHITE - RGB(254,254,254) * this way, the whites will remain without the need of a mask loColorMap.OldColor = .Color.White loColorMap.NewColor = .Color.FromARGB(255,254,254,254) loAttr.SetRemapTable(loColorMap) loGfx = .Graphics.FromImage(loBmp) loRect = loBmp.GetBounds() loGfx.DrawImage(loBmp, m.loRect, m.loRect, .GraphicsUnit.Pixel, loAttr) * Next step, convert the borders to pure White, RGB(255,255,255) that will become transparent in buttons loColorMap.OldColor = loBorderClr loColorMap.NewColor = .Color.White loAttr.SetRemapTable(loColorMap) loGfx.DrawImage(loBmp, m.loRect, m.loRect, .GraphicsUnit.Pixel, loAttr) ENDIF * Continue, cropping each icon from the big image FOR N = 0 TO (loBmp.WIDTH / lnWidth) - 1 lcIconFile = lcNewFile + TRANSFORM(x) + "_Icon_" + TRANSFORM(N) + ".bmp" loIcon = loBmp.CLONE(.Rectangle.New(N * lnWidth, 0, lnWidth, lnHeight), .Imaging.PixelFormat.Format24bppRGB) IF VARTYPE(loIcon) = "O" loIcon.SAVE(lcIconFile, .Imaging.ImageFormat.Bmp) ENDIF ENDFOR *-* END OF THE BMP CROPPING ENDFOR FreeLibrary(hModule) * Show progression lnRec = RECNO() lnPercentage = ROUND((lnRec / lnTotRec),2) IF lnPercentage > lnOldPercentage WAIT WINDOW "Extracting icons from files..." + CHR(13) + ; "Elapsed: " + TRANSFORM(lnPercentage * 100) + " %" NOWAIT lnOldPercentage = lnPercentage ENDIF ENDSCAN ENDWITH RETURN ***************************************************************************************** * FUNCTION : RECURSE * AUTHOR : MICHAEL REYNOLDS * DESCRIPTION : Good for performing file processing throughout an entire directory tree. * The function, RECURSE(), is called with the full path of a directory. * RECURSE() will then read all files and directories in the path. * A function can be called to process files that it finds. * Plus, the function calls itself to process any sub-directories. * http://fox.wikis.com/wc.dll?Wiki~RecursiveDirectoryProcessing~VFP ***************************************************************************************** FUNCTION Recurse(pcDir) LOCAL lnPtr, lnFileCount, laFileList, lcDir, lcFile CHDIR (pcDir) DIMENSION laFileList[1] *--- Read the chosen directory. lnFileCount = ADIR(laFileList, '*.*', 'D') FOR lnPtr = 1 TO lnFileCount IF 'D' $ laFileList[lnPtr, 5] *--- Get directory name. lcDir = laFileList[lnPtr, 1] *--- Ignore current and parent directory pointers. IF lcDir != '.' AND lcDir != '..' *--- Call this routine again. Recurse(lcDir) ENDIF ELSE *--- Get the file name. lcFile = LOWER(FULLPATH(laFileList[lnPtr, 1])) *--- Insert into cursor if .EXE, .ICO or .DLL IF INLIST(LOWER(JUSTEXT(lcFile)),"dll","exe") INSERT INTO Recursive (cFile) VALUES (lcFile) ENDIF ENDIF ENDFOR *--- Move back to parent directory. CHDIR .. ENDFUNC
Cesar!
ReplyDeleteWhat a wonderful piece of code! I'm sure I will be able to find some good uses for the capabilities you expose.
Thank you!
Malcolm
Hi Marcolm.
Kudos to Calvin. I've just brought the main idea to "our world". But I agree with you on the usability of this. I'm still making tests here, but for sure lots of cool things can be obtained just adapting the codes shown here.
One point that I wanted to better emphasize is to show people that it is really easy to take advantage of the codes created by people from other languages, specially .NET and Visual Basic.
Regards
Cesar
Nice article. I wrote one that is related, maybe not a masterpiece like yours, but its my first time alone with the GDIPLUSX classes!
ReplyDeletehttp://www.ctl32.com.ar/articles/playing_with_gdiplusx.asp
Hi Carlos,
GREAT ARTICLE !
My true inspiration for this article was a comment of yours that I found in Calvin's blog. The aproaches of the two articles are different. I've learned some cool things from it.
BTW, I'll add a link to it to our GdiPlusX "articles" page in Codeplex.
Regards
Cesar
Main Object to extract VFP9 Icon for the Caption of Forms for Own .Exe when run above code error popup
ReplyDeleteCannot find Entry Point deleteobject in the DLL
Thanks !
ReplyDeleteWhen this blog was moved to WordPress, all the Casing markups were lost. I fixed the relevant code from this post. In other posts, we can have the same issues,
unfortunately.
Regards