I have been playing with the GDI+ flat api lately and here are a few useful functions in GoAsm, by the weekend I will have the GDI+ headers translated and upload them to the headers project for now these examples only take a single structure definition...
// These will be included in gdiplus.h or related header files
#dynamiclinkfile gdiplus.dll
ImageCodecInfo struct
Clsid GUID <>
FormatID GUID <>
CodecName DD
DllName DD
FormatDescription DD
FilenameExtension DD
MimeType DD
Flags DD
Version DD
SigCount DD
SigSize DD
SigPattern DD
SigMask DD
ENDS
// The memcopy function is referenced in GetEncoderClsid and MemToHBitmap
memcopy FRAME lpSource,lpDest,nBytes
uses edi,esi,ecx
cld
mov edi,[lpDest]
mov esi,[lpSource]
mov ecx,[nBytes]
; do the evenly divisible ones
shr ecx,2
rep movsd
; do the remainder
mov ecx,[nBytes]
and ecx,3
rep movsb
RET
ENDF
LoadPicturePathA FRAME lpPath
LOCAL wsz[MAX_PATH+1] :W
LOCAL pBitmap :D
LOCAL hBitmap :D
invoke MultiByteToWideChar,CP_ACP,NULL,[lpPath],-1,offset wsz,MAX_PATH
invoke GdipCreateBitmapFromFile,offset wsz,offset pBitmap
invoke GdipCreateHBITMAPFromBitmap,[pBitmap],offset hBitmap,0FFFFFFFFh
invoke GdipDisposeImage,[pBitmap]
mov eax,[hBitmap]
RET
ENDF
SaveHBitmapToFileA FRAME hBitmap, pFileName, format
LOCAL wsz[MAX_PATH+1] :W
LOCAL pImage :D
LOCAL hPal :D
LOCAL pszEncoder :D
LOCAL wfmt[MAX_PATH] :W
LOCAL encoderClsid :GUID
/*
// format constants
#DEFINE GDIP_IMAGE_BMP 0
#DEFINE GDIP_IMAGE_JPG 1
#DEFINE GDIP_IMAGE_GIF 2
#DEFINE GDIP_IMAGE_EMF 3
#DEFINE GDIP_IMAGE_WMF 4
#DEFINE GDIP_IMAGE_TIFF 5
#DEFINE GDIP_IMAGE_PNG 6
#DEFINE GDIP_IMAGE_ICON 7
// Or pass a pointer to a NULL terminated ansi encoder type string ie "image/png"
*/
invoke GdipCreateBitmapFromHBITMAP,[hBitmap],NULL,offset pImage
test eax,eax
jnz >>.NOIMG
cmp D[format],GDIP_IMAGE_BMP
jne >
mov D[pszEncoder],offset STRENCBMP
jmp >>.GETENC
:
cmp D[format],GDIP_IMAGE_GIF
jne >
mov D[pszEncoder],offset STRENCGIF
jmp >>.GETENC
:
cmp D[format],GDIP_IMAGE_JPG
jne >
mov D[pszEncoder],offset STRENCJPG
jmp >>.GETENC
:
cmp D[format],GDIP_IMAGE_TIFF
jne >
mov D[pszEncoder],offset STRENCTIF
jmp >>.GETENC
:
cmp D[format],GDIP_IMAGE_PNG
jne >
mov D[pszEncoder],offset STRENCPNG
jmp >>.GETENC
:
invoke IsBadReadPtr,[format],1
test eax,eax
jnz >>.ERRORFORMAT
invoke MultiByteToWideChar,CP_ACP,NULL,[format],-1,offset wfmt,MAX_PATH
lea eax,wfmt
mov [pszEncoder],eax
.GETENC
invoke GetEncoderClsid,[pszEncoder], offset encoderClsid
test eax,eax
js >>.ERRORFORMAT
invoke MultiByteToWideChar,CP_ACP,NULL,[pFileName],-1,offset wsz,MAX_PATH
invoke GdipSaveImageToFile,[pImage], offset wsz,offset encoderClsid,NULL
test eax,eax
jnz >>.NOSAVE
invoke GdipDisposeImage,[pImage]
invoke SetLastError,0
xor eax,eax
RET
.NOIMG
invoke SetLastError,ERROR_INVALID_HANDLE
xor eax,eax
dec eax
RET
.ERRORFORMAT
invoke GdipDisposeImage,[pImage]
invoke SetLastError,ERROR_NOT_SUPPORTED
xor eax,eax
dec eax
RET
.NOSAVE
invoke GdipDisposeImage,[pImage]
// GdipSaveImageToFile seems to set the error code properly so leave it
xor eax,eax
dec eax
RET
STRENCBMP: DUS "image/bmp",0
STRENCGIF: DUS "image/gif",0
STRENCJPG: DUS "image/jpeg",0
STRENCTIF: DUS "image/tiff",0
STRENCPNG: DUS "image/png",0
ENDF
GetEncoderClsid FRAME format, pClsid
uses edi,ebx,esi
LOCAL num :D // number of image encoders
LOCAL size :D // size of the image encoder array in bytes
LOCAL pImageCodecInfo :D
mov D[num],0
mov D[size],0
invoke GdipGetImageEncodersSize, offset num, offset size
cmp D[size],0
je >>.ERROR
invoke GdipAlloc,[size]
mov [pImageCodecInfo],eax
test eax,eax
jz >>.ERROR
// Scan through the codecs
invoke GdipGetImageEncoders,[num],[size],[pImageCodecInfo]
xor ebx,ebx
dec ebx
.NEXTCODEC
inc ebx
cmp ebx,[num]
jg >.EXIT
imul esi,ebx,SIZEOF ImageCodecInfo
add esi,ImageCodecInfo.MimeType
add esi,[pImageCodecInfo]
invoke lstrcmpiW,[esi],[format]
test eax,eax
jnz <.NEXTCODEC
imul esi,ebx,SIZEOF ImageCodecInfo
add esi,[pImageCodecInfo]
add esi, ImageCodecInfo.Clsid
invoke memcopy,esi,[pClsid],SIZEOF GUID
invoke GdipFree,[pImageCodecInfo]
mov eax,ebx
ret
.EXIT
invoke GdipFree,[pImageCodecInfo]
.ERROR
xor eax,eax
dec eax
ret
ENDF
HIconToHBitmap FRAME hIcon
LOCAL pBitmap :D
LOCAL hBitmap :D
invoke GdipCreateBitmapFromHICON,[hIcon],offset pBitmap
invoke GdipCreateHBITMAPFromBitmap,[pBitmap],offset hBitmap,0FFFFFFFFh
invoke GdipDisposeImage,[pBitmap]
mov eax,[hBitmap]
RET
ENDF
MemToHBitmap FRAME pMemory,dwSize
LOCAL pStream :D
LOCAL pGlobal :D
LOCAL pBitmap :D
LOCAL hBitmap :D
mov D[pStream],NULL
invoke CoTaskMemAlloc, [dwSize]
test eax,eax
jnz >
; no memory available
xor eax,eax
ret
:
mov [pGlobal],eax
invoke memcopy,[pMemory],[pGlobal],[dwSize]
invoke CreateStreamOnHGlobal, [pGlobal], TRUE, ADDR pStream
test eax,eax
jz >
; no interface
invoke CoTaskMemFree,[pGlobal]
xor eax,eax
ret
:
invoke GdipCreateBitmapFromStream,[pStream],offset pBitmap
invoke GdipCreateHBITMAPFromBitmap,[pBitmap],offset hBitmap,0FFFFFFFFh
invoke GdipDisposeImage,[pBitmap]
CoInvoke(pStream,IStream.IUnknown.Release)
mov eax,[hBitmap]
RET
ENDF
Excellent Donkey, thanks for sharing. Another great addition for your website. :bg
The whole headers project is a little confusing though... I'm using the IncludeA/IncludeW includes. Is that the "old" version?
Donkey, you could make it simpler without GetEncoderClsid function, by linking already defined encoder GUID's from gdiplus.lib:
// GdiplusImaging.h
ImageEncoderBmp
ImageEncoderJpeg
ImageEncoderGif
ImageEncoderTiff
ImageEncoderPng
Or even by replacing the 'format' parameter with encoder guid pointer:
invoke SaveHBitmapToFileA, hBitmap, path, ImageEncoderPng
Hi Mark,
Yes, the header project has gotten quite large from the first idea I had for it, but the goal of matching the MSDN help files as closely as possible as well as support for all versions up to XP (and a little Vista) have steadily pushed it to its current state. The Windows.H file was meant to make things a bit easier but the number of switches keep growing. Obviously I only use the .H files for my projects as I prefer them but I can see why it would be easier. Note that in the headers project there are no more .INC files, this is to match the naming convention at MSDN as well as distinguish them from the older Window.inc and IncludeA/IncludeW, though those files are still available I have not added to them or corrected them in years. For the header files you have only to include Windows.H as well as any non-standard header files that you might need for your project (usually listed at MSDN). If you are using the LINKFILES switch, you no longer need to have gfl.txt in your command line or decalre any DLLs as that is taken care of by the headers, Unicode is handled by the STRINGS UNICODE switch automatically so the files are no longer split.
Hi arkane,
Still translating the header files, however, I wanted to write the function anyway in case there are other installed 3rd party codecs in addition to the 5 internal ones.
Nice work Edgar :U
Rendering an image to a normal control is pretty much the most important thing to do with GDI+, drawing an image object is pretty useless if you can't display it. Here's a simple way to render a gdi plus image object to a device context. Note that the x and y coordinates are the top and left borders.
DrawGDIPImage FRAME hdc, pImage, x, y
LOCAL pGraphics :D
invoke GdipCreateFromHDC,[hdc], offset pGraphics
invoke GdipDrawImageI,[pGraphics], [pImage], [x], [y]
invoke GdipDeleteGraphics, [pGraphics]
xor eax,eax
RET
ENDF
The following will stretch the image to fit :
DrawStretchedGDIPImage FRAME hdc, pImage, x, y, w, h
LOCAL pGraphics :D
invoke GdipCreateFromHDC,[hdc], offset pGraphics
invoke GdipDrawImageRectI,[pGraphics], [pImage], [x], [y], [w], [h]
invoke GdipDeleteGraphics, [pGraphics]
xor eax,eax
RET
ENDF
In your WM_PAINT handler they would look something like this, I have found that GDIPlus makes for very readable and clean code.
cmp D[uMsg],WM_PAINT
jne >>.DEFPROC
invoke BeginPaint,[hwnd],offset ps
invoke GdipCreateFromHDC,[ps.hdc], offset pGraphics
invoke GetClientRect,[hwnd],offset rect
invoke GdipDrawImageRectI,[pGraphics], [pImage], [rect.left], [rect.top], [rect.right], [rect.bottom]
invoke GdipDeleteGraphics, [pGraphics]
invoke EndPaint,[hwnd],offset ps
xor eax,eax
ret
The flat API as described at MSDN is not all available to the redistributable version of GDIPlus, many of the documented functions are only available in a "microsoft only" version that ships with Office and a few other packages. One of those packages is the Power Point Viewer available for free from MS, it comes with version 6.0.3264.0 which has all of the documented functions. However, to use it you have to include a manifest, this is the manifest I used to have Windows recognize the newer version of gdiplus...
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<noInherit/>
<assemblyIdentity
processorArchitecture="x86"
type="win32"
name="Gdip_test"
version="1.0.0.0"/>
<description>GDIPlus test program</description>
<file name="gdiplus.dll"/>
</assembly>
The additional functions are pretty nice, like GdipBitmapConvertFormat which allows you to change color depths and convert to grayscale. Just place gdiplus.dll version 6.0.3264.0 in the same folder as your application and include the manifest in your resource file.
If you need common controls version 6 in addition to the newer GDI plus, your manifest files would be...
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<noInherit/>
<assemblyIdentity
processorArchitecture="x86"
type="win32"
name="Gdip_test"
version="1.0.0.0"/>
<description>GDIPlus test program</description>
<dependency optional="yes">
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.1.0"
publicKeyToken="6595b64144ccf1df"
language="*"
processorArchitecture="x86"/>
</dependentAssembly>
</dependency>
<file name="gdiplus.dll"/>
</assembly>
I have searched Microsoft and cannot find any indication that there are even alternate versions of GDIPlus for the unwashed masses and Microsoft internally let alone if we are allowed to distribute it with applications, not even an example of the manifest anywhere on the web that I can find so I don't think this information is for "public consumption".
It would really be helpful if we had a Microsoft Insider that could help with queries such as these.
Hi Mark,
Yeah, that would be nice.
Using the newer version (v6.0.3264.0) of GDIPlus we can do easy color depth conversions, to do them we have to create an appropriate palette and call GdipBitmapConvertFormat. Here is the SaveHBitmapToFileA procedure with color depth conversion added to it. Supported bit depths are 8, 16*, 24, 32, 48* and 64* (*=bmp only), alpha channel is not supported but easy to add if you need it, some software (notably irfanview) cannot display 64 Bpp but the images can be viewed using Windows picture and fax viewer, for some reason 48Bpp seems to result in a 32 Bpp image. Setting the color depth to -1 bypasses the conversion completely.
SaveHBitmapToFileA FRAME hBitmap, pFileName, format, bitdepth
LOCAL wsz[MAX_PATH+1] :W
LOCAL pImage :D
LOCAL pszEncoder :D
LOCAL pPal :D
LOCAL wfmt[MAX_PATH] :W
LOCAL encoderClsid :GUID
LOCAL PalType :D
LOCAL ImgFmt :D
/*
// format constants
#DEFINE GDIP_IMAGE_BMP 0
#DEFINE GDIP_IMAGE_GIF 1
#DEFINE GDIP_IMAGE_JPG 2
#DEFINE GDIP_IMAGE_TIFF 3
#DEFINE GDIP_IMAGE_PNG 4
*/
invoke GdipCreateBitmapFromHBITMAP,[hBitmap],NULL,offset pImage
test eax,eax
jnz >>.NOIMG
cmp D[format],GDIP_IMAGE_BMP
jne >
mov D[pszEncoder],offset STRENCBMP
jmp >>.GETENC
:
cmp D[format],GDIP_IMAGE_GIF
jne >
mov D[pszEncoder],offset STRENCGIF
jmp >>.GETENC
:
cmp D[format],GDIP_IMAGE_JPG
jne >
mov D[pszEncoder],offset STRENCJPG
jmp >>.GETENC
:
cmp D[format],GDIP_IMAGE_TIFF
jne >
mov D[pszEncoder],offset STRENCTIF
jmp >>.GETENC
:
cmp D[format],GDIP_IMAGE_PNG
jne >
mov D[pszEncoder],offset STRENCPNG
jmp >>.GETENC
:
invoke IsBadReadPtr,[format],1
test eax,eax
jnz >>.ERRORFORMAT
invoke MultiByteToWideChar,CP_ACP,NULL,[format],-1,offset wfmt,MAX_PATH
lea eax,wfmt
mov [pszEncoder],eax
.GETENC
invoke GetEncoderClsid,[pszEncoder], offset encoderClsid
test eax,eax
js >>.ERRORFORMAT
invoke MultiByteToWideChar,CP_ACP,NULL,[pFileName],-1,offset wsz,MAX_PATH
// The largest palette size is 1024 bytes so we allocate that
invoke GdipAlloc,1024 + SIZEOF ColorPalette
mov [pPal],eax
cmp D[bitdepth],-1
je >>.NoConvert
cmp D[bitdepth],8
jne >
// Since this is an indexed type use a large palette to find the closest colors
// it will be reduced to 256 colors by the conversion function
mov D[PalType],PaletteTypeFixedHalftone27
mov D[ImgFmt],PixelFormat8bppIndexed
jmp >>.Convert
:
cmp D[bitdepth],24
jne >
mov D[PalType],PaletteTypeFixedHalftone27
mov D[ImgFmt],PixelFormat24bppRGB
jmp >>.Convert
:
cmp D[bitdepth],32
jne >
mov D[PalType],PaletteTypeFixedHalftone256
mov D[ImgFmt],PixelFormat32bppRGB
:
cmp D[format],GDIP_IMAGE_BMP
jne >>.NoConvert
// Bitmap only color depths
cmp D[bitdepth],16
jne >
// There is no standard 16Bpp palette so we use a 24 Bpp. It's not optimal but easier.
mov D[PalType],PaletteTypeFixedHalftone27
mov D[ImgFmt],PixelFormat16bppRGB555
jmp >>.Convert
:
cmp D[bitdepth],48
jne >
mov D[PalType],PaletteTypeFixedHalftone256
mov D[ImgFmt],PixelFormat48bppRGB
:
cmp D[bitdepth],64
jne >.NoConvert
mov D[PalType],PaletteTypeFixedHalftone256
mov D[ImgFmt],PixelFormat64bppARGB
.Convert
invoke GdipInitializePalette,[pPal], [PalType], 0, TRUE, NULL
invoke GdipBitmapConvertFormat,[pImage],[ImgFmt], DitherTypeNone,[PalType],[pPal],50.0
.NoConvert
invoke GdipSaveImageToFile,[pImage], offset wsz,offset encoderClsid,NULL
test eax,eax
jnz >>.NOSAVE
invoke GdipDisposeImage,[pImage]
invoke GdipFree,[pPal]
invoke SetLastError,0
xor eax,eax
RET
.NOIMG
invoke SetLastError,ERROR_INVALID_HANDLE
xor eax,eax
dec eax
RET
.ERRORFORMAT
invoke GdipDisposeImage,[pImage]
invoke SetLastError,ERROR_NOT_SUPPORTED
xor eax,eax
dec eax
RET
.NOSAVE
invoke GdipDisposeImage,[pImage]
invoke GdipFree,[pPal]
// GdipSaveImageToFile seems to set the error code properly so leave it
xor eax,eax
dec eax
RET
STRENCBMP: DUS "image/bmp",0
STRENCGIF: DUS "image/gif",0
STRENCJPG: DUS "image/jpeg",0
STRENCTIF: DUS "image/tiff",0
STRENCPNG: DUS "image/png",0
ENDF
Applying effects to bitmaps is pretty confusing, mostly because you have to push a complete GUID (not it's address) onto the stack this caused me quite a few headaches while I was trying to figure out effect. BTW the headers are getting along, I should post them tomorrow.
// This will be in macros.a
#IFNDEF pushguid
pushguid(%1) MACRO
push [%1+12]
push [%1+8]
push [%1+4]
push [%1]
ENDM
#ENDIF
// The following are defined in GDIPLUSEFFECTS.H
#define GUID_BlurEffectGuid <0x633c80a4, 0x1843, 0x482b, 0x9e, 0xf2, 0xbe, 0x28, 0x34, 0xc5, 0xfd, 0xd4>
#define GUID_BrightnessContrastEffectGuid <0xd3a1dbe1, 0x8ec4, 0x4c17, 0x9f, 0x4c, 0xea, 0x97, 0xad, 0x1c, 0x34, 0x3d>
BlurParams struct
radius DD
expandEdge DD
ENDS
BrightnessContrastParams struct
brightnessLevel DD
contrastLevel DD
ENDS
So much for the definitions, here's a simple image blur routine, you pass a pointer to a GDI+ bitmap object and a radius (REAL4) between 0.0 and 255.0 to it, the higher the radius the greater the blurring.
DATA SECTION
BlurEffectGuid GUID GUID_BlurEffectGuid
BrightnessContrastEffectGuid GUID GUID_BrightnessContrastEffectGuid
CODE SECTION
BlurApplyEffect FRAME pBitmap,radius
LOCAL blurparams :BlurParams
LOCAL pEffect :D
LOCAL rect :RECT
mov eax,[radius]
mov D[blurparams.radius], eax
mov D[blurparams.expandEdge], FALSE
lea eax,pEffect
push eax
pushguid(BlurEffectGuid)
call GdipCreateEffect
invoke GdipSetEffectParameters,[pEffect],offset blurparams,SIZEOF BlurParams
mov D[rect.left],0
mov D[rect.top],0
invoke GdipGetImageWidth,[pBitmap],offset rect.right
invoke GdipGetImageHeight,[pBitmap],offset rect.bottom
invoke GdipBitmapApplyEffect,[pBitmap],[pEffect],offset rect, NULL,NULL,NULL
invoke GdipDeleteEffect,[pEffect]
RET
ENDF
This skeleton will work with all effects, for example the brightness contrast effect...
BrightnessContrastApplyEffect FRAME pBitmap,Brightness,Contrast
LOCAL bcparams :BrightnessContrastParams
LOCAL pEffect :D
LOCAL rect :RECT
mov eax,[Brightness]
mov [bcparams.brightnessLevel], eax
mov eax,[Contrast]
mov [bcparams.contrastLevel], eax
lea eax,pEffect
push eax
pushguid(BrightnessContrastEffectGuid)
call GdipCreateEffect
invoke GdipSetEffectParameters,[pEffect],offset bcparams,SIZEOF BrightnessContrastParams
mov D[rect.left],0
mov D[rect.top],0
invoke GdipGetImageWidth,[pBitmap],offset rect.right
invoke GdipGetImageHeight,[pBitmap],offset rect.bottom
invoke GdipBitmapApplyEffect,[pBitmap],[pEffect],offset rect, NULL,NULL,NULL
invoke GdipDeleteEffect,[pEffect]
RET
ENDF
I had some time today to test the newer version of GDIPlus on Windows 98SE, none of the version 6 only functions worked but the version 5 ones seemed to be OK. The OS didn't cough when I tried to execute a version 6 function, it just didn't do anything so conversion and effects are not available as part of the flat API for anything below XP.