home
Using Picture Objects

Ernest Murphy ernie@surfree.com

June 30 2001

Sample code for this article is available.

Abstract:
------------------------------------------------------------------------------ 
The COM API provides methods to load many useful picture formats, at least
.bmp. .ico, .wmf, .gif, and .,jpg. The use, care and feeding of this interface will 
be explained
 

Introduction:
------------------------------------------------------------------------------ 

This tutorial is based on the work in Q218972 - How To Load and Display Graphics Files w/LOADPIC.EXE. Basically, all this tutorial consists of is a port of that C code to asm (no, not by a program, by the "see C, write ASM method).

There are quite a few steps to actually get a picture object, but these are always the same and may be contained in a procedure such as in the tutorial. 

What's a Picture Object Anyway?
------------------------------------------------------------------------------ 
Picture objects are the COM/OLE standard way of loading picture files for ActiveX use. It's a nice, well behaved API you can use in any application.

A Picture object is any object that provides the IPicture interface. And IPicture does plenty of work for us. Let's look at it's properties and methods:
 

get_Handle Returns the Windows GDI handle of the picture managed within this picture object. 
get_Hpal Returns a copy of the palette currently used by the picture object. 
get_Type Returns the current type of the picture. 
get_Width Returns the current width of the picture in the picture object. 
get_Height  Returns the current height of the picture in the picture object. 
Render  Draws the specified portion of the picture onto the specified device context, positioned at the specified location. 
set_Hpal Sets the current palette of the picture. 
get_CurDC  Returns the current device context into which this picture is selected. 
SelectPicture  Selects a bitmap picture into a given device context, returning the device context in which the picture was previously selected as well as the picture's GDI handle. 
get_KeepOriginalFormat  Returns the current value of the picture object's KeepOriginalFormat property. 
put_KeepOriginalFormat  Sets the picture object's KeepOriginalFormat property. 
PictureChanged  Notifies the picture object that its picture resource changed. 
SaveAsFile  Saves the picture's data into a stream in the same format that it would save itself into a file. 
get_Attributes  Returns the current set of the picture's bit attributes.

That's quite a few goodies to work with for one package. True, many seem to be wrappers for existing GDI methods, but here they are all contained in one nice package (hey, maybe that's why objects are so useful)
 

Creating a Picture Object
------------------------------------------------------------------------------ 
A Picture object is created from a stream object, which is created from a.... OK, let's just jump to the code.
 
 
LoadPictureFile PROC pszFile:DWORD, ppPicture:DWORD
    LOCAL hFile:DWORD, pvData:DWORD,     dwBytesRead:DWORD
    LOCAL pstm:DWORD,  dwFileSize:DWORD, hGlobal:DWORD
    LOCAL ghWnd:DWORD, bRead:DWORD

; This function loads a file into an IStream.
    ; open file
    invoke CreateFile, pszFile, GENERIC_READ, 0, NULL,
           OPEN_EXISTING, 0, NULL
    mov hFile, eax
    .IF hFile == INVALID_HANDLE_VALUE 
        ; we had an error
        mov eax, -1
        ret
    .ENDIF

    ; get file size
    invoke GetFileSize, hFile, NULL
    mov dwFileSize, eax
    .IF dwFileSize == -1 
        ; we had an error
        invoke CloseHandle, hFile
        mov eax, -1
        ret
    .ENDIF

    mov pvData, NULL
    ; alloc memory based on file size
    invoke GlobalAlloc, GMEM_MOVEABLE, dwFileSize
    mov hGlobal, eax
    .IF !hGlobal        ; we had an error
        invoke CloseHandle, hFile
        mov eax, -1
        ret
    .ENDIF

    invoke GlobalLock, hGlobal
    mov pvData , eax
    .IF !pvData        ; we had an error
        ret
    .ENDIF

    mov dwBytesRead, NULL
    ; read file and store in global memory
    invoke ReadFile, hFile, pvData, dwFileSize,
                     ADDR dwBytesRead, NULL
    mov bRead, eax
    .IF bRead == FALSE        ; we had an error
        ret
    .ENDIF

    invoke GlobalUnlock, hGlobal
    invoke CloseHandle, hFile

    mov pstm, NULL
    ; create IStream* from global memory
    invoke CreateStreamOnHGlobal, hGlobal, TRUE, ADDR pstm
    .IF_FAILED
        ret
    .ENDIF
    .IF !pstm
        ret
    .ENDIF

    ; Create IPicture from image file
    .IF ppPicture
        mov ecx, ppPicture
        mov ecx, [ecx]
        .IF ecx;gpPicture1
            coinvoke ecx, IUnknown, Release
        .ENDIF
        invoke OleLoadPicture, pstm, dwFileSize, FALSE,
               ADDR IID_IPicture, ppPicture
        .IF_FAILED
            coinvoke pstm, IUnknown, Release
            ret
        .ENDIF
    .ENDIF

    coinvoke pstm, IUnknown, Release

    ret
LoadPictureFile ENDP
;----------------------------------------------
 

The LoadPictureFile procedure takes a character string (psz) that contains the picture's filename, and assigns the picture object pointer to our pic object. It's smart enough to release an existing pic object if you pass one in. Actually, there is much more error handling in this routine then is usual, but I wanted to make this a reusable routine for you.

All we did here is use standard API methods to move a file into global memory. The first new COM method is the CreateStreamOnHGlobal method. A "stream" is OLE-speak for a storage medium where data may be streamed in or out. Of course, being a COM method it returns a stream object. Since all we need do is pass this on and release it, we'll pretend it isn't there.

The big action takes place with OleLoadPicture. This method will create the picture object for us from the stream object.
 
 

Using the Picture Object
------------------------------------------------------------------------------ 
Once we have the picture object (which we call to be created inside the WM_CREATE message handler) we want to use it. IPicture does have a very nice Render method that is akin to a BitBlt instruction, in that it copies a selected portion of the Picture contained in the object to the device context you choose.

One real bit of true weirdness in this Render method is vertically it works backwards! I mean, if you have a 20x30 pixel bitmap and instruct it to render it starting at 0,0 and copy 20x30 piece, you will get an image that is upside down. I have no reason to explain this, just a work around. When Rendering this picture, instead tell it to place it at 0x30 (i.e., down by the Y dimension) and to copy 20x-30 pixels (i.e., work up and not down). 

The other bit of predictable weirdness is the Picture object works in high metric units, so we need to convert these.

So let's go to the code:
 
 
 .ELSEIF uMsg==WM_PAINT
    invoke BeginPaint, hWnd, ADDR ps
    mov hdc, eax
    .IF gpPicture1
        ; get width and height of picture
        coinvoke gpPicture1, IPicture, get_Width, 
                        ADDR hmWidth
        coinvoke gpPicture1, IPicture, get_Height, 
                        ADDR hmHeight

        ; convert himetric to pixels
        invoke GetDeviceCaps,hdc, LOGPIXELSX
        invoke MulDiv, hmWidth, eax, HIMETRIC_INCH
        mov nWidth, eax

        invoke GetDeviceCaps,hdc, LOGPIXELSY
        invoke MulDiv, hmHeight, eax, HIMETRIC_INCH
        mov nHeight, eax

        xor eax, eax
        sub eax, hmHeight
        mov neghmHeight, eax

        invoke GetClientRect, hWnd, ADDR rect
        ; display picture using IPicture::Render
        coinvoke gpPicture1, IPicture, Render, hdc, 0, 0,
                  nWidth, nHeight, 0, hmHeight, 
                  hmWidth, neghmHeight, ADDR rect

    .ENDIF
    invoke EndPaint, hWnd, ADDR ps

.ELSEIF
 

That's about it, except for one last bit of clean-up. When your app exits, and if you still have a picture object handy, you need to release it like so:
 
 
.IF gpPicture1
      coinvoke gpPicture1, IUnknown, Release
.ENDIF

 

------------------------------------------------------------------------------ 
I hope you get some work out of this useful object.

Enjoy
 

home