UPDATE March 15, 2003:
I recently had a need to convert BMP files that had already been read into memory as-is, to bitmap handles for use by the Win32 GDI functions. I used the techniques described below, plus a IPicture method "GPget_Handle()". Code and description in this file: bitmap_handle_from_memory.txt.
I have also recently discovered a webpage that explains low-level details about COM, that I have not found anywhere else. The page describes how to create a HTML control and embed it in a window. Code is in C. URL: http://www.codeguru.com/ieprogram/cwebpage.html. The tutorial part on its own is excellent.
When I was developing the early beta versions of my new Windows application, EVE (see http://goosee.com), a vector graphics drawing program, it needed to be able to load and insert bitmap graphics images. That is, I wanted to load a graphic file and display it in my application's window.
Now, there are libraries available to do this, some of them free. They don't have source code, except for one, called GIFLIB,
with MASM source code -- see a link to it from Hutch's MASM site. GIFLIB only loads GIF files.
I used it for the first release of my EVE product, however it is a bit buggy and crashes on some GIFs -- the LZW decompressor is the part that crashes.
After much hunting around, I found a technical article on Microsoft's web site, describing a program called LOADPIC -- you should be able to locate it from "LOADPIC" as a keyword. This is a C++ program that uses the OleLoadPicture() function. This function is very high-level and will convert GIF, JPG, BMP and WMF files into a format that can be displayed, including the decompressing.
This function is not described in Microsoft's SDKs prior to 1998, though the docs state that it works on Win95 -- I haven't tested my program on a wide range of Win95 systems -- the function is in OLEPRO32.DLL and it may not be in older Win95 systems, though I suspect that most will have it, as it is likely that a later application will have loaded it when it got installed.
The problem is, I knew nothing about OLE (Object Linking and Embedding) and COM (Common Object Model). For MASM programmers, there's a whole lot of stuff on this topic at Hutch's asm site (or was that Iczelion's site? -- whatever), submitted by Ernie Murphy. Also, this COM stuff is at Ernie's own site: http://here.is/COMinASM.
Ok, to tackle this, I took the LOADPIC.CCP application and compiled it using the free Borland C++ compiler -- it gave lots
of compile errors, because the code was designed for Microsoft's Visual C++. But, I had set a switch on the compiler to
generate an asm file, and at least the compiler got as far as that.
Armed with the asm file, I was able to figure out the code required for my program, however, the key function, OleLoadPicture(), returned an error code. So, I emailed my code to Ernie for his perusal. This greatly intrigued Ernie, as he had no idea that there was such a function to load graphics images (as is the case for just about every other Win32 programmer). Ernie has converted LOADPIC.CCP to LOADPIC.ASM and it is now available at his site.
Ernie's approach is very professional, and you have to include lots of macros, include files and link in various library files. I successfully assembled his example program, but I decided to have another look at my original "grassroots" solution. I discovered the bug that was preventing OleLoadPicture() to work, so I have placed my code below. The only extra libraries that you have to link to are OLEPRO32.LIB and OLE32.LIB -- you can find these from various places.
You'll need these two prototypes:
CreateStreamOnHGlobal PROTO WINAPI, :DWORD, :DWORD, :DWORD
OleLoadPicture PROTO WINAPI, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD
I'm talking MASM code here, but you should be able to translate to C or whatever.
I'll leave it up to you to write the code to open a file and load it into a memory block. Note that Ernie's example has the code for that. Let's say we have loaded the file and have variable "pmem" pointing to the memory block...
Here is my grassroots code for converting the file into a form that can be displayed:
IID_IPicture DWORD 07bf80980h ;unsigned long.
WORD 0bf32h ;unsigned short.
WORD 0101ah ;unsigned short
BYTE 08bh,0bbh,0,0aah,0,030h,0ch,0abh ;unsigned char Data4;
gpPicture DWORD 0
E_POINTER EQU 080000005h
E_NOINTERFACE EQU 080000004h
E_OUTOFMEMORY EQU 080000002h
creategifhdc PROC STDCALL USES ebx ecx edx esi edi, \
;ret eax=0 ok, else error#.
;we enter func with pmem->bitmap-file-in-memory.
For the OLE/COM functions to use it, the memory block has to be converted into something called a "stream" -- don't ask me what the difference is ...
;now need to convert the mem-block into a "stream"...
invoke CreateStreamOnHGlobal,hglobal,TRUE,ADDR pstm
;...returns pstm =ptr to stream (i.e. copy of the mem block).
;returns eax=0 if ok
.IF eax != 0
When I created the memory block, I saved its size, which I'm retrieving here, and calling the magical function OleLoadPicture(). IID_IPicture is a structure that defines what type of information is to be loaded, in this case a graphic image. This function will place a pointer into variable gpPicture, that points to an "interface" -- this is basically a structure, with addresses of "methods" (functions) that can manipulate the data...
mov eax,mygifinfo.dwGIFDataSize ;size of mem block.
invoke OleLoadPicture,pstm,eax,0,ADDR IID_IPicture,ADDR gpPicture
;..."IID_IPicture" is the type of interface pointer to return.
;...The requested interface pointer is returned in "gpPicture".
;returns eax=0 if ok
.IF eax != 0 ;S_OK=0
.IF SDWORD PTR eax<0
mov eax,221 ;E_UNEXPECTED
I don't need the "stream" anymore. Also, it wasn't in the original LOADPIC code, but I presumed that I also needed to free the original memory block (does releasing the stream also delete the original memory block? -- I have no idea). Release() is a method, and has to be accessed in this manner (see Ernie's code for a more formal way of doing this)...
mov eax,pstm ;pstm->Release();
push eax ;/
mov edx,dword ptr [eax] ;/
call dword ptr [edx+8] ;/
;umm, presume can release original mem block?...
I need to call some more methods to get the width and height of the graphic, but they are
returned in "himetric" units. I have converted these to pixels. Earlier in my program, in the
handling of the WM_CREATE message, I had calculated the variables xscreenpixels and
yscreenpixels, using this code:
;find out the resolution of the display...
invoke GetDC, g_hwnd ;-->eax=display context client area.
invoke GetDeviceCaps, hdc1,LOGPIXELSX ;query pixels/inch.
invoke GetDeviceCaps, hdc1,LOGPIXELSY ;query pixels/inch.
invoke ReleaseDC, g_hwnd,hdc1
So, I can convert the himetric units to pixels...
;the picture is now ready to be painted... gpPicture points (indirectly) to it...
lea eax,hmwidth ;gpPicture->get_Width(&hmWidth)
push eax ;/
mov edx,dword ptr gpPicture ;/
push edx ;/
mov ecx,dword ptr [edx] ;/ (hmwidth is in himetric units)
call dword ptr [ecx+24] ;/
lea eax,hmheight ;gpPicture->get_Height(&hmHeight)
push eax ;/
mov edx,dword ptr gpPicture ;/
push edx ;/
mov ecx,dword ptr [edx] ;/
call dword ptr [ecx+28] ;/ (hmheight is in himetric units)
;the wwidth and height are in HIMETRIC units. for output to screen, need
;the width/height in pixels...
This section of code below is just for my own EVE program. I have an element that is to display on the screen and it has to hold gpPicture, the coordinates of the element on the diagram, and width/height in both pixels and himetric units. When EVE processes the WM_PAINT message, this info is retrieved from the element...
;the picture is going to be painted in common_paint_bitmap(), and need to
;setup the icon now and pass stuff...
mov (OBJECT PTR [ebx]).ximage,eax
;also, save width/height of bitmap into icon...
;and update xmid/ymid...
mov (OBJECT PTR [edi]).wwidth,eax
add eax,(OBJECT PTR [edi]).xtopleft
mov (OBJECT PTR [edi]).xmid,eax
mov (OBJECT PTR [edi]).height,eax
add eax,(OBJECT PTR [edi]).ytopleft
mov (OBJECT PTR [edi]).ymid,eax
;also need to save wwidth/height of image in 1st data-element...
mov eax,(OBJECT PTR [edi]).wwidth
mov WORD PTR (OBJECT PTR [ebx]).nshape,ax ;(alias for rplock2).
mov eax,(OBJECT PTR [edi]).height
mov WORD PTR (OBJECT PTR [ebx]).nshape+2,ax
;save the width/height in himetric units in the icon...
mov (OBJECT PTR [edi]).widthhimetric,eax
mov (OBJECT PTR [edi]).heighthimetric,eax
xor eax,eax ;ok.
er1: mov eax,219 ;error.
Ok, so we've got this gpPicture pointer and the width/height of the image, in both himetric units and pixels. When the application processes the WM_PAINT message, you can have this code:
;width/height in pixels...
mov esi,(OBJECT PTR [edi]).wwidth
mov ebx,(OBJECT PTR [edi]).height
;width/height in himetric units...
mov esi,(OBJECT PTR [edi]).widthhimetric
mov ebx,(OBJECT PTR [edi]).heighthimetric
mov eax,(OBJECT PTR [ebx]).ximage ;gpPicture ;hDCBitmap
mov ecx,(OBJECT PTR [edi]).xtopleft
mov edx,(OBJECT PTR [edi]).ytopleft
; // display picture using IPicture::Render
; gpPicture->Render(hdc, 0, 0, nWidth, nHeight, 0, hmHeight, hmWidth, -hmHeight, &rc);
; lea ebx,clientrect
; push ebx ;0 ;really need addr of client rect here. ? (for wmf file)
mov ebx,heighthimetric ;needs to be -ve, otherwise picture
neg ebx ;displays upside down. see also below topcorner.
push ebx ;heighthimetric ;y height to copy from source picture. himetric
push widthhimetric ;x width to copy from source picture. units.
push heighthimetric ;upper-left corner of source, NOTE, not 0.
push 0 ; / himetric units.
push heightpixels ;height diagram coords.
push widthpixels ;wwidth /
push edx ;0 ;y upper-left corner,
push ecx ;0 ;x diagram coords.
push hdc ;hdc.
; mov eax,dword ptr gpPicture
mov edx,dword ptr [eax]
call dword ptr [edx+32]
Ok, so as my program draws each element to the screen, it gets the gpPicture variable and the width/height parameters out. Then we call a method of the IPicture interface, called Render().
The basic structure of the code described above allows more than one picture to be simultaneously loaded and displayed. However, gpPicture points to an "interface" that points to methods (functions) as well as the actual bitmap data. So, when finished it is necessary to release the interface. My EVE program flushes these gpPicture's whenever a diagram is saved, as well as when the EVE program is exited. Here is the code to release a gpPicture interface:
mov eax,(OBJECT PTR [esi]).ximage
.IF eax != 0
mov edx,eax ;gpPicture ;gpPicture->Release();
push edx ;/ "this" has to be passed, which is a ptr
mov ecx,dword ptr [edx] ;/ to the interface.
call dword ptr [ecx+8] ;/
mov (OBJECT PTR [esi]).ximage,0
What EVE does is look through the elements in the database, and the .ximage parameter has gpPicture if the entry is non-zero. I then call the Release() method. Simple.
There you are, it works real nice. Have a look at my EVE site: http://goosee.com/ .