News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Using GDI+ (GDIplus)

Started by jorgon, September 07, 2006, 07:43:03 AM

Previous topic - Next topic

jorgon

I've been tinkering a little with GDI+ (GDIplus) which offers some really handy graphics functions under XP (or from W98 upwards if loaded on the system as a redistributable).  I thought it would be useful to report my findings.

The MS documentation is very obscure to assembler programmers since it describes GDI+ in terms of a C/C++ class-based interface, which means there is a lot more going on in the background prior to making the final call to the API itself than appears in the source code.  This can be followed from the header files which come with the SDK and there is some description in the MS documentation but its easy to miss essential components.

MS does list the APIs themselves which it calls the "Flat API" in this article and of course you can view the names of the APIs by looking inside Gdiplus.dll (which in my system ended up in the Windows\WinSxS folder) using a PE viewer like Wayne Radburn's PEView.  Although MS recommend that you use the C/C++ wrappers rather than calling the API directly it does not explain why.

Maybe someone will create similar assembler wrappers but calling the API directly does work, and as usual with Windows programming, once you have one step on the ladder, everything else begins to fall into place.  The current problem is lack of source code examples and poor documentation.

There is some useful cataloging of the Flat API making the functions easier to find in Jose Roca's Power Basic site.
There is also some well commented assembler source describing various uses of the Flat API, together with assembler include files created by Alonso Murillo available from the RadASM projects page and there is also a posting on this board here which is material originally posted by xzazet and enlarged by dougiem.

Since neither of the above sources has a simple example using the DrawImage "method" I thought I would post my source for using this.  It might act as a useful starting point for members of the forum to get into GDI+.  The following code draws a picture file to an existing window.  The exciting thing is that the picture file could be formatted as BMP, GIF, JPEG, Exif, PNG, TIFF, ICON, WMF, or EMF.


ALIGN 4
GdiplusStartupInput DD 1     ;GdiplusVersion
                    DD 0     ;DebugEventCallback
                    DD 0     ;SuppressBackgroundThread
                    DD 0     ;SuppressExternalCodecs
GDIPLUSTOKEN        DD 0     ;receives GDI+ token on initialise GDI+
GRAPHICS_OBJECT     DD 0     ;holds a pointer to a "Graphics" object
IMAGE_OBJECT        DD 0     ;holds a pointer to an "Image" object
;
INITIALISE_GDIPLUS:
INVOKE GdiplusStartup,ADDR GDIPLUSTOKEN,ADDR GdiplusStartupInput,0
RET
;
CLOSE_GDIPLUS:
CALL GdiplusShutdown,[GDIPLUSTOKEN]
RET
;
PAINT:
INVOKE BeginPaint,[hWnd],ADDR PAINTSTRUCT
INVOKE GdipCreateFromHDC,EAX,ADDR GRAPHICS_OBJECT
INVOKE GdipLoadImageFromFile,L'Mypic.bmp',ADDR IMAGE_OBJECT
INVOKE GdipDrawImageI,[GRAPHICS_OBJECT],[IMAGE_OBJECT],0,0
INVOKE GdipDisposeImage,[IMAGE_OBJECT]
INVOKE GdipDeleteGraphics,[GRAPHICS_OBJECT]
INVOKE EndPaint,[hWnd],ADDR PAINTSTRUCT
RET


Here is a brief explanation of the above code.
Before using GDI+ it needs to be initialised, so if you were using GDI+ from time to time throughout your program, you would call the INITIALISE_GDIPLUS function soon after program entry. You would call CLOSE_GDIPLUS on closure (probably on the main window WM_DESTROY message).
You would call the PAINT function on the WM_PAINT message.  Although GDI+ can work independently from device contexts, it is necessary to call BeginPaint and EndPaint as usual to stop recurring WM_PAINT messages.  Since BeginPaint returns the device context in eax we can use this by giving it to the GdipCreateFromHDC API.  This creates a GDI+ "Graphics Object" and returns a pointer to it in GRAPHICS_OBJECT.  Then GdipLoadImageFromFile creates a GDI+ "Image Object" and returns a pointer to it in IMAGE_OBJECT.  Note that a pointer to a picture file filename is given to this function (in Unicode).  GdipDrawImageI then draws the image to the window at co-ordinates x=0 and y=0. GdipDrawImageI is just one of the drawing APIs in the DrawImage method.  Finally GdipDeleteGraphics cleans up. 
EDIT: 8 Sept 2006 - add GdipDisposeImage to clear-up completely.

All these APIs return a value in eax.  If this is zero then the call was successful.  A list of error codes is available here.

When linking a file using GDI+ don't forget to include the line "Gdiplus.dll" in the linker's command file if using GoLink.  Other linkers will probably need a lib file.

Author of the "Go" tools (GoAsm, GoLink, GoRC, GoBug)

jorgon

The following code, developed further from the previous code, can display an image file which is already in memory or which is held in the data section:-


UNKNOWN STRUCT
   QueryInterface DD 0
   AddRef         DD 0
   Release        DD 0
UNKNOWN ends
;
IStream STRUCT
IUnknown          UNKNOWN
Read              DD 0
Write             DD 0
Seek              DD 0
SetSize           DD 0
CopyTo            DD 0
Commit            DD 0
Revert            DD 0
LockRegion        DD 0
UnlockRegion      DD 0
Stat              DD 0
Clone             DD 0
ENDS

DATA
ALIGN 4
ISTREAM DD 0      ;interface for IStream object

CODE
;call this at program start to initialize the COM interface:-
INVOKE CoInitialize,0

;in the following code called on WM_PAINT, esi=image file address, ebx=image file size in bytes

INVOKE BeginPaint,[hWnd],ADDR PAINTSTRUCT
INVOKE GdipCreateFromHDC,EAX,ADDR GRAPHICS_OBJECT
INVOKE CreateStreamOnHGlobal,0,1,ADDR ISTREAM
CoInvoke(ISTREAM,IStream.Write,ESI,EBX,0)
INVOKE GdipLoadImageFromStream,[ISTREAM],ADDR IMAGE_OBJECT
INVOKE GdipDrawImageI,[GRAPHICS_OBJECT],[IMAGE_OBJECT],0,0
INVOKE GdipDisposeImage,[IMAGE_OBJECT]
INVOKE GdipDeleteGraphics,[GRAPHICS_OBJECT]
CoInvoke(ISTREAM.IStream.IUnknown.Release)
INVOKE EndPaint,[hWnd],ADDR PAINTSTRUCT

;call this at program end to release the COM interface:-
INVOKE CoUninitialize


The code above uses both GDI+ and COM.

COM is used to create a "stream" which is an area of memory organised and controlled by the system.  The API call CreateStreamOnHGlobal actually creates the stream (this API is in Ole32.dll).  This places a pointer to the interface of the stream object in ISTREAM, which must be used to access the stream.  Next it is necessary to write the image data to the stream, and this is done using the COM call IStream::Write.  In fact, in order to make this COM call I've used Donkey's method for calling the COM interface using the CoInvoke macro.  This is described in his excellent WinExplorer files.  The call makes use of the IStream and UNKNOWN structures, but in these arrangements the structures are never actually implemented.  Instead they are used merely to resolve to a number selecting the correct call to make from the virtual table ("vtable") which lists the various calls available for that particular interface.

Then, back to GDI+, the call to the API GdipLoadImageFromStream gets ready for the drawing of the image as before.

Finally, the GDI+ image and graphics objects are released and the ISTREAM interface is also released using another COM call.
Author of the "Go" tools (GoAsm, GoLink, GoRC, GoBug)

Vortex

#2
Hi Jeremy,

Thanks for the example codes. They are nice demonstrations on how to use basic GDI+ functions :U

Infro_X

I must say, well done. Also, I'd advise against trying to use GDI+ to do text output for things like edit controls (or really anything that you'd have to line up text perfectly (or near perfection), There isn't any way to measure where the next charecter should go, easily. (You'll find out if you try). ;)

Infro_X

There are some other things that I've found out using GDI+ in cpp.
I'm pretty sure that GdiplusStartup has to be called in the Thread that is going to use the graphics object.
Its a com thing (You can't CoInitialize and CoCreateInstance in different threads or something very similiar).

ramguru

Thanks for snippet, jorgon!

Quote from: jorgon on September 07, 2006, 07:43:03 AM
Since neither of the above sources has a simple example using the DrawImage "method" I thought I would post my source for using this.  It might act as a useful starting point for members of the forum to get into GDI+.  The following code draws a picture file to an existing window.  The exciting thing is that the picture file could be formatted as BMP, GIF, JPEG, Exif, PNG, TIFF, ICON, WMF, or EMF.
unfortunately there are some limitations, for example, GDI+ couldn't show ICON (probably not supported format). It cannot show BMP from resource, and I know why if you declare BITMAP "my_pic.bmp", resource compiler will erase BITMAPFILEHEADER and save the rest as raw data in executable, luckily BITMAPFILEHEADER is recoverable  :wink And one more thing regarding BMP, GDI+ couldn't show bitmap with alpha (32 color model)

Infro_X

you tried SetBkMode transparent?
SetBkMode(g_hDC, TRANSPARENT);

It might have something to do with it ;)