Here's a finished app (well, testbed) that displays any JPEG image in its window. The image is sized to the window (which can be resized) within a 10-pixel border. Pretty much what I was aiming for.
;============================================
; ShowPic - GDI+ testbed for displaying JPEGs
;
;============================================
.nolist
include \masm32\include\masm32rt.inc
include \masm32\include\gdiplus.inc
includelib \masm32\lib\gdiplus.lib
.list
;============================================
; Defines, macros, prototypes, etc.
;============================================
WinMain PROTO :DWORD
ScaleImg2Win PROTO scaleImgStructPtr:DWORD
MakeUnicodeName PROTO asciiName:DWORD, unicodeName:DWORD
OFDialog PROTO hWin:HWND, fileOpenNamePtr:DWORD, fileOpenStartPath:DWORD, dialogTitle:DWORD, \
fileFilterPtr:DWORD
strcpy PROTO dest:DWORD, source:DWORD
$mainWinWidth EQU 800
$mainWinHeight EQU 600
;===== Window styles: =====
$mainWinStyles EQU WS_OVERLAPPEDWINDOW or WS_CLIPCHILDREN or WS_VISIBLE or WS_SIZEBOX
;===== Window background colors: =====
$bkRED EQU 254
$bkGRN EQU 243
$bkBLUE EQU 199
$BkColor EQU $bkRED OR ($bkGRN SHL 8) OR ($bkBLUE SHL 16)
$menuOpen EQU 2001
$menuExit EQU 2002
$imgStruct STRUCT
imgW DD ?
imgH DD ?
imgX DD ?
imgY DD ?
winW DD ?
winH DD ?
$imgStruct ENDS
$scaleC EQU 1000 ;Scaling constant
$border EQU 10
;============================================
; HERE BE DATA
;============================================
.data
ImgStruct $imgStruct <>
ScaleC DD $scaleC
WC WNDCLASSEX < SIZEOF WNDCLASSEX, \
CS_HREDRAW or CS_VREDRAW or CS_BYTEALIGNWINDOW, \
NULL, \ ;lpfnWndProc
NULL, \ ;cbClsExtra
NULL, \ ;cbWndExtra
NULL, \ ;hInstance
NULL, \ ;hIcon
NULL, \ ;hCursor
NULL, \ ;hbrBackground
NULL, \ ;lpszMenuName
NULL, \ ;lpszClassName
NULL > ;hIconSm
GDIinputStruct GdiplusStartupInput <1, NULL, FALSE, 0>
MainClassName DB "ShowPic", 0
MainTitleText DB "GDI+ Testbed for Displaying JPEGs", 0
MenuFileStr DB "File", 0
MenuOpenStr DB "Open...", 0
MenuExitStr DB "Exit", 0
FileOpenDlgTitle DB "Select JPEG File to View", 0
FileOpenStartPathStr DB ".\*."
DefaultExtension DB "jpg", 0
JPGfilters LABEL BYTE
DB "JPEG files (*.jpg, *.jpeg)", 0, "*.jpg;*.jpeg", 0
DB "All files", 0, "*.*", 0, 0
;============================================
; UNINITIALIZED DATA
;============================================
.data?
MainWinHandle HWND ?
GDItoken DD ?
GdiHbitmap DD ?
;============================================
; CODE LIVES HERE
;============================================
.code
start: INVOKE GetModuleHandle, NULL
MOV WC.hInstance, EAX
INVOKE WinMain, EAX
INVOKE ExitProcess, EAX
;====================================================================
; Mainline proc
;====================================================================
WinMain PROC hInst:DWORD
LOCAL msg:MSG, brush:HBRUSH, wX:DWORD, wY:DWORD, gpRect:RECT
LOCAL hDC:HDC, gdiGraphics:DWORD, gdiHgraphics:DWORD
LOCAL bmWidth:DWORD, bmHeight:DWORD
; Create brush to set background color:
INVOKE CreateSolidBrush, $BkColor
MOV brush, EAX
; Register class for parent window:
MOV WC.lpfnWndProc, OFFSET MainWindowProc
MOV EAX, brush
MOV WC.hbrBackground, EAX
MOV WC.lpszClassName, OFFSET MainClassName
MOV WC.hIcon, NULL
INVOKE LoadCursor, NULL, IDC_ARROW
MOV WC.hCursor, EAX
INVOKE RegisterClassEx, OFFSET WC
INVOKE GetSystemMetrics, SM_CXSCREEN
MOV EDX, $mainWinWidth
CALL CenterDim
MOV wX, EAX
INVOKE GetSystemMetrics, SM_CYSCREEN
MOV EDX, $mainWinHeight
CALL CenterDim
MOV wY, EAX
; Create our main window:
INVOKE CreateWindowEx, WS_EX_OVERLAPPEDWINDOW, OFFSET MainClassName,
OFFSET MainTitleText, $mainWinStyles, wX, wY, $mainWinWidth,
$mainWinHeight, NULL, NULL, hInst, NULL
MOV MainWinHandle, EAX
;******** Initialize GDI+: ********
INVOKE GdiplusStartup, OFFSET GDItoken, OFFSET GDIinputStruct, NULL
; Get a DC:
INVOKE GetDC, MainWinHandle
MOV hDC, EAX
; Get graphics "object" from DC handle:
INVOKE GdipCreateFromHDC, hDC, ADDR gdiHgraphics
;============= Message loop ===================
msgloop:
INVOKE GetMessage, ADDR msg, NULL, 0, 0
OR EAX, EAX ;EAX = 0 = exit
JZ exit99
INVOKE TranslateMessage, ADDR msg
INVOKE DispatchMessage, ADDR msg
JMP msgloop
exit99: INVOKE GdiplusShutdown, GDItoken
MOV EAX, msg.wParam
RET
WinMain ENDP
;====================================================================
; Main Window Proc
;====================================================================
MainWindowProc PROC hWin:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
LOCAL hDC:HDC, gdiHgraphics:DWORD, ps:PAINTSTRUCT
LOCAL hMenu:HMENU, subMenu1:HMENU, fileOpenName[MAX_PATH]:BYTE
LOCAL unicodeName[MAX_PATH * 2]:BYTE, gpRect:RECT
MOV EAX, uMsg
CMP EAX, WM_COMMAND
JE do_command
CMP EAX, WM_PAINT
JE do_paint
CMP EAX, WM_CLOSE
JE do_close
CMP EAX, WM_CREATE
JE do_create
dodefault:
; use DefWindowProc for all other messages:
INVOKE DefWindowProc, hWin, uMsg, wParam, lParam
RET
;==============================
; WM_COMMAND handler
;==============================
do_command:
MOV AX, WORD PTR wParam
CMP AX, $menuOpen
JE do_openfile
CMP AX, $menuExit
JE do_close
JMP dodefault
do_openfile:
INVOKE OFDialog, hWin, ADDR fileOpenName, OFFSET FileOpenStartPathStr, OFFSET FileOpenDlgTitle,
OFFSET JPGfilters
OR EAX, EAX ;Did user select a file?
JZ dodefault ; Nope.
; File selected--must make filename Unicode:
INVOKE MakeUnicodeName, ADDR fileOpenName, ADDR unicodeName
; Open JPEG image file:
INVOKE GdipLoadImageFromFile, ADDR unicodeName, OFFSET GdiHbitmap
; Get width & height of bitmap:
INVOKE GdipGetImageWidth, GdiHbitmap, OFFSET ImgStruct.imgW
INVOKE GdipGetImageHeight, GdiHbitmap, OFFSET ImgStruct.imgH
; Get width & height of display window:
INVOKE GetClientRect, hWin, ADDR gpRect
MOV EAX, gpRect.right
SUB EAX, gpRect.left
SUB EAX, $border * 2
MOV ImgStruct.winW, EAX
MOV EAX, gpRect.bottom
SUB EAX, gpRect.top
SUB EAX, $border * 2
MOV ImgStruct.winH, EAX
; Scale image to our window:
INVOKE ScaleImg2Win, OFFSET ImgStruct
INVOKE InvalidateRect, hWin, NULL, TRUE ;Force paint.
XOR EAX, EAX
RET
;==============================
; WM_CREATE handler
;==============================
do_create:
; Creating the menu here "on the fly" avoids the need for a resource file:
CALL CreateMenu
MOV hMenu, EAX
CALL CreatePopupMenu
MOV subMenu1, EAX
INVOKE AppendMenu, hMenu, MF_POPUP or MF_STRING, subMenu1, OFFSET MenuFileStr
INVOKE AppendMenu, subMenu1, MF_STRING, $menuOpen, OFFSET MenuOpenStr
INVOKE AppendMenu, subMenu1, MF_STRING, $menuExit, OFFSET MenuExitStr
INVOKE SetMenu, hWin, hMenu
XOR EAX, EAX
RET
;==============================
; WM_PAINT handler
;==============================
do_paint:
INVOKE BeginPaint, hWin, ADDR ps
MOV hDC, EAX
; Apparently this isn't needed:
; INVOKE SetGraphicsMode, hDC, GM_ADVANCED
; Get graphics "object" from DC handle:
INVOKE GdipCreateFromHDC, hDC, ADDR gdiHgraphics
; Display image at (X,Y) with dimensions (W,H):
; Many thanks to "mabdelouahab" from the MASM32 forum for this.
INVOKE GdipDrawImageRectI, gdiHgraphics, GdiHbitmap,
ImgStruct.imgX, ImgStruct.imgY, ImgStruct.imgW, ImgStruct.imgH
INVOKE EndPaint, hWin, ADDR ps
commonexit:
XOR EAX, EAX
RET
do_close:
INVOKE PostQuitMessage, NULL
MOV EAX, TRUE
RET
MainWindowProc ENDP
;=====================================================
; ScaleImg2Win (imgStructPtr)
;
; Given the dimensions of an image and a window,
; scales the image up or down as needed to fit
; within the window and sets the origin (X,Y).
;
; All arguments are passed via the $imgStruct.
;=====================================================
ScaleImg2Win PROC imgStructPtr:DWORD
LOCAL wScale:DWORD, hScale:DWORD
PUSH EBX
MOV EBX, imgStructPtr
; Calculate width & height conversion factors:
MOV EAX, [EBX].$imgStruct.winW
MUL ScaleC
XOR EDX, EDX
DIV [EBX].$imgStruct.imgW
MOV wScale, EAX
MOV EAX, [EBX].$imgStruct.winH
MUL ScaleC
XOR EDX, EDX
DIV [EBX].$imgStruct.imgH
MOV hScale, EAX
; Which factor is smaller?
CMP EAX, wScale
JB useH ;Height, so scale to it.
MOV EAX, wScale
useH: MOV ECX, EAX ;ECX = scale factor.
; Scale to selected factor:
MOV EAX, [EBX].$imgStruct.imgW
MUL ECX
XOR EDX, EDX
DIV ScaleC
MOV [EBX].$imgStruct.imgW, EAX
MOV EAX, [EBX].$imgStruct.imgH
MUL ECX
XOR EDX, EDX
DIV ScaleC
MOV [EBX].$imgStruct.imgH, EAX
; Now determine upper-left corner of image:
MOV EAX, [EBX].$imgStruct.winW
SUB EAX, [EBX].$imgStruct.imgW
JNC @F ;Check for result <0.
XOR EAX, EAX
@@: SHR EAX, 1 ; / 2.
ADD EAX, $border
MOV [EBX].$imgStruct.imgX, EAX
MOV EAX, [EBX].$imgStruct.winH
SUB EAX, [EBX].$imgStruct.imgH
JNC @F
XOR EAX, EAX
@@: SHR EAX, 1
ADD EAX, $border
MOV [EBX].$imgStruct.imgY, EAX
POP EBX
RET
ScaleImg2Win ENDP
;=====================================================
; OFDialog (hWin, fileOpenNamePtr, fileOpenStartPath, dialogTitle, fileFilterPtr)
;
; Basically a wrapper for GetOpenFileName()
; Sets up needed structure, invokes file-selection dialog.
;
; On exit,
; EAX = return value (zero if user cancelled or
; selected no file)
; EDX = offset of filename from pathname (returned
; in fileOpenNamePtr)
;=====================================================
OFDialog PROC hWin:HWND, fileOpenNamePtr:DWORD, fileOpenStartPath:DWORD, dialogTitle:DWORD,
fileFilterPtr:DWORD
LOCAL ofn:OPENFILENAME
; First, zero out entire structure:
PUSH EDI
LEA EDI, ofn
MOV ECX, SIZEOF OPENFILENAME
XOR EAX, EAX
REP STOSB ;Zero it out.
POP EDI
MOV ofn.lStructSize, SIZEOF OPENFILENAME
MOV EAX, hWin
MOV ofn.hWndOwner, EAX
MOV EAX, WC.hInstance
MOV ofn.hInstance, EAX
MOV EAX, fileOpenNamePtr
MOV ofn.lpstrFile, EAX
INVOKE strcpy, EAX, fileOpenStartPath
MOV ofn.nMaxFile, MAX_PATH
MOV EAX, dialogTitle
MOV ofn.lpstrTitle, EAX
MOV EAX, fileFilterPtr
MOV ofn.lpstrFilter, EAX
MOV ofn.Flags, OFN_EXPLORER OR OFN_FILEMUSTEXIST OR OFN_LONGNAMES OR OFN_HIDEREADONLY
INVOKE GetOpenFileName, ADDR ofn
MOVZX EDX, ofn.nFileOffset
RET
OFDialog ENDP
;=====================================================
; MakeUnicodeName (asciiName, unicodeName)
;
; Magically turns a plain ASCII string into a Unicode one.
; Arguments are pointers to strings.
;=====================================================
MakeUnicodeName PROC asciiName:DWORD, unicodeName:DWORD
PUSH ESI
PUSH EDI
MOV ESI, asciiName
MOV EDI, unicodeName
XOR AH, AH
@@: LODSB
STOSW
OR AX, AX
JNZ @B
STOSW ;Terminator.
POP EDI
POP ESI
RET
MakeUnicodeName ENDP
;====================================================================
; strcpy (dest, source)
;====================================================================
strcpy PROC dest:DWORD, source:DWORD
PUSH ESI
PUSH EDI
MOV ESI, source
MOV EDI, dest
scp10: LODSB
STOSB
OR AL, AL ;Stop on NULL.
JNZ scp10
scp99: POP EDI
POP ESI
RET
strcpy ENDP
;====================================================================
; CenterDim
;
; Returns half screen dimension (X or Y) minus half window dimension
;
; On entry,
; EAX = screen dimension
; EDX = window dimension
;
; Returns:
; sdim/2 - wdim/2
;====================================================================
CenterDim PROC
SHR EAX, 1 ;divide screen dimension by 2
SHR EDX, 1 ;divide window dimension by 2
SUB EAX, EDX
RET ;Return w/# in EAX
CenterDim ENDP
END start