News:

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

A few GDI+ examples

Started by donkey, January 13, 2009, 06:10:06 PM

Previous topic - Next topic

donkey

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
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

Mark Jones

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?
"To deny our impulses... foolish; to revel in them, chaos." MCJ 2003.08

akane

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

donkey

#3
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.
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

Vortex


donkey

#5
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
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

donkey

#6
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>   
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

donkey

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".
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

Mark Jones

It would really be helpful if we had a Microsoft Insider that could help with queries such as these.
"To deny our impulses... foolish; to revel in them, chaos." MCJ 2003.08

donkey

Hi Mark,

Yeah, that would be nice.

"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

donkey

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
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

donkey

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
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

donkey

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.
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable